Introduction
You can use a lambda expression for representing anonymous methods. We use it for creating instances of functional interfaces (interfaces with a single abstract method). When the lambda expression just calls an existing method, we can use a Method Reference.
A method reference is a reference to an existing method. They are more concise than a lambda expression as they have a name. They thus improve readability. In this post, we will see the different types of method references along with code examples.
Types of Method References
There are four kinds of method references
- Method reference to a static method
- Method reference to an instance method of a particular object
- Method reference to an instance method of an arbitrary object of a particular type
- Method reference to a constructor
Our Example
We will use a List of Person objects to illustrate method references
public class Person {
private String name;
private int age;
private Gender gender;
public Person(String name, int age, Gender gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Gender getGender() {
return gender;
}
@Override
public String toString() {
return name;
}
}
```java
List<Person> persons = Arrays.asList(new Person("Andrew", 18, Gender.MALE),
new Person("Tom", 28, Gender.MALE),
new Person("Lisa", 21, Gender.FEMALE),
new Person("Robert", 24, Gender.MALE),
new Person("Mary", 38, Gender.FEMALE));
Method reference to a static method
Let us filter the persons whose gender is male. Using Java 8 streams we can do like
List<Person> malePersons = persons.stream()
.filter(person -> person.getGender() == Gender.MALE)
.collect(Collectors.toList());
Say we have a method called isMale. It takes a Person object and returns true if the Person is a male and false otherwise.
private static boolean isMale(Person person) {
return person.getGender() == Gender.MALE;
}
We can make use of the above method within the filter condition.
malePersons = persons.stream()
.filter(person -> isMale(person))
.collect(Collectors.toList());
By using a method reference, we can achieve the same by
malePersons = persons.stream()
.filter(Main::isMale)
.collect(Collectors.toList());
Here, Main is the name of the class. Main::isMale is a method reference. The static method will be called by passing the lambda parameters as arguments.
Other forms of lambda expressions that can be reduced to this form of method reference are
() -> Class.staticMethod()
(arg1) -> Class.staticMethod(arg1)
(arg1, arg2) -> Class.staticMethod(arg1, arg2)
Method reference to an instance method of an object
We will create a class PersonComparator that compares two Person objects (a Comparator for Person objects)
public class PersonComparator {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
}
We will sort the Persons list using this comparator.
PersonComparator personComparator = new PersonComparator();
persons.sort((a, b) -> personComparator.compareByName(a, b));
Using a method reference, we can write like
persons.sort(personComparator::compareByName);
In this form of method reference, all the lambda parameters will be passed as arguments to the method being called(compareByName).
Other forms of lambda expressions that can be reduced to this form of method reference are:
() -> obj.instanceMethod()
(arg1) -> obj.instanceMethod(arg1)
(arg1, arg2) -> obj.instanceMethod(arg1, arg2)
Method reference to an instance method of an arbitrary object of a particular type
Let us stream the person list, map each person to his/her name and collect the names as a list.
List<String> names = persons.stream()
.map(person -> person.getName())
.collect(Collectors.toList());
Using method reference,
names = persons.stream()
.map(Person::getName)
.collect(Collectors.toList());
Person::getName has replaced the lambda expression person -> person.getName()
. We refer to the getName method of the Person class.
Here, the first lambda parameter is the object on which we call the method specified in the method reference (getName). The rest of the lambda parameters (if present) will be passed as arguments to the method.
Example with two lambda parameters
To make this concrete, let us see usage of this with more than one lambda parameter. In addition to mapping each Person to a name, let us sort the names.
List<String> sortedNames = persons.stream()
.map(Person::getName)
.sorted(String::compareTo) //sorted((p1, p2) -> p1.compareTo(p2))
.collect(Collectors.toList());
The method reference String::compareTo is used in place of the lambda (p1, p2) -> p1.compareTo(p2)
.
The compareTo method will be called on the first lambda parameter (p1) and the other parameters (p2) are passed as arguments.
Other forms of lambda expressions related to this type are
(arg1) -> arg1.instanceMethod()
(arg1, arg2) -> arg1.instanceMethod(arg2)
(arg1, arg2, arg3) -> arg1.instanceMethod(arg2, arg3)
Note: A lambda expression without any parameters is not applicable to this type of method reference.
Method reference to a constructor
After mapping each person to a name, let us collect the names in a HashSet.
Set<String> namesAsSet = persons.stream()
.map(Person::getName)
.collect(Collectors.toCollection(() -> new HashSet<>()));
Using method reference, we can replace new HashSet<>() with HashSet::new.
namesAsSet = persons.stream()
.map(Person::getName)
.collect(Collectors.toCollection(HashSet::new));
Other forms of lambda expressions applicable are
() -> new MyClass()
(arg1) -> new MyClass(arg1)
(arg1, arg2) -> new MyClass(arg1, arg2)
Conclusion
In conclusion, we can use Method references in place of lambda expressions that just call an existing method passing parameters. The method references are more readable than the lambda expression and therefore they are more expressive.
I have used method references in my other posts too. Please do check them out.
References
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
https://howtodoinjava.com/java8/lambda-method-references-example/