Java Method References

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;
    }
}
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 as follows:

(arg1) -> arg1.instanceMethod()
(arg1, arg2) -> arg1.instanceMethod(arg2)
(arg1, arg2, arg3) -> arg1.instanceMethod(arg2, arg3)

Note: However, a lambda expression without any parameters (as seen for the other types) 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.

Java 8 IntStream

Java Comparator Comparing

References

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

https://howtodoinjava.com/java8/lambda-method-references-example/

https://www.baeldung.com/java-method-references

Leave a Reply