One of the most welcome changes in Java 8 was the introduction of lambda expressions, as these allow us to forego anonymous classes, greatly reducing boilerplate code and improving readability.
Method references are a special type of lambda expressions. They’re often used to create simple lambda expressions by referencing existing methods.
There are four kinds of method references:
- Static methods
- Instance methods of particular objects
- Instance methods of an arbitrary object of a particular type
- Constructor
In this tutorial, we’ll explore method references in Java.
1. Reference to a Static Method
We’ll begin with a very simple example, capitalizing and printing a list of Strings
List<String> wordList = Arrays.asList("hello", "hitechpoints", "readers!");
We can achieve this by leveraging a simple lambda expression calling the StringUtils.upperCase() method directly:
wordList.forEach(word -> StringUtils.upperCase(word));
Or, we can use a method reference to simply refer to the upperCase static method:
messages.forEach(StringUtils::upperCase);
Notice that method references always utilize the :: operator.
2. Reference to an Instance Method of a Particular Object
To demonstrate this type of method reference, let’s consider two classes
public class Car {
private String brand;
private int price;
public Car(String brand, Integer price) {
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
import java.util.Comparator;
public class CarComparator implements Comparator<Car> {
@Override
public int compare(Car c1, Car c2) {
return c1.getPrice().compareTo(c2.getPrice());
}
}
And, let’s create a CarComparator object to compare car price:
CarComparator carPriceComparator = new CarComparator();
We could use a lambda expression to sort car by price, but we’d need to specify two cars for comparison:
List<Car> carList = new ArrayList<Car>();
carList.add(new Car("BMW X1", 52000000));
carList.add(new Car("BMW X7", 152000000));
carList.stream().sorted((c1, c2) -> carPriceComparator.compare(c1, c2));
Instead, we can use a method reference to have the compiler handle parameter passing for us
carList.stream().sorted(carPriceComparator::compare);
The method reference is much cleaner and more readable, as our intention is clearly shown by the code.
3. Reference to an Instance Method of an Arbitrary Object of a Particular Type
This type of method reference is similar to the previous example, but without having to create a custom object to perform the comparison.
Let’s create an Integer list that we want to sort
List<Integer> list = Arrays.asList(5, 3, 50, 24, 40, 2, 9, 18);
If we use a classic lambda expression, both parameters need to be explicitly passed, while using a method reference is much more straightforward
numbers.stream().sorted((a, b) -> a.compareTo(b));
numbers.stream().sorted(Integer::compareTo);
Even though it’s still a one-liner, the method reference is much easier to read and understand.
4. Reference to a Constructor
We can reference a constructor in the same way that we referenced a static method in our first example. The only difference is that we’ll use the new keyword.
Let’s create a Car array out of a String list with different brands
List<String> list = Arrays.asList("BMW X1", "Audi", "Honda");
Then, we’ll add a new constructor to our Car class
public Car(String brand) {
this.brand = brand;
this.price = 0;
}
Next, we’ll use our new constructor from a method reference and make a Car array from the original String list
list.stream()
.map(Car::new)
.toArray(Car[]::new);
Notice how we called both Car and Array constructors using a method reference, giving our code a much more concise and clear appearance.