Skip to content

Method References

Using Lambda expressions allows us to avoid creating implementation classes for FunctionalInterface, thus simplifying the code:

java
Arrays.sort(array, (s1, s2) -> {
    return s1.compareTo(s2);
});

In fact, in addition to Lambda expressions, we can also directly pass method references. For example:

java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);
        System.out.println(String.join(", ", array));
    }

    static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

In the code above, we directly pass the reference of the static method cmp to Arrays.sort() using Main::cmp.

Definition of Method Reference

Method references allow you to pass a method directly if its signature matches that of the interface. Since the Comparator<String> interface defines the method int compare(String, String), and the static method int cmp(String, String) matches this signature (with the same parameter types and return type), we can use the method name as a Lambda expression:

java
Arrays.sort(array, Main::cmp);

Note: Here, the method signature only considers the parameter types and return types; it does not take the method name or class inheritance into account.

Referencing Instance Methods

Now let’s see how to reference instance methods. If we rewrite the code as follows:

java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, String::compareTo);
        System.out.println(String.join(", ", array));
    }
}

This not only compiles successfully but also produces the same result at runtime, indicating that String.compareTo() method is also compatible with the Lambda definition.

Let’s examine the method definition of String.compareTo():

java
public final class String {
    public int compareTo(String o) {
        ...
    }
}

This method has only one parameter, so how does it match int Comparator<String>.compare(String, String)?

The reason is that instance methods have an implicit this parameter. The compareTo() method of the String class, when invoked, always passes the first implicit parameter as this, which is akin to a static method:

java
public static int compareTo(String this, String o);

Thus, String.compareTo() can also be used as a method reference.

Constructor References

In addition to referencing static and instance methods, we can also reference constructor methods.

Let’s consider an example where we want to convert a List<String> to a List<Person>. How can we achieve this?

java
class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

The traditional way would be to define an ArrayList<Person> and fill it using a for loop:

java
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = new ArrayList<>();
for (String name : names) {
    persons.add(new Person(name));
}

To implement the conversion from String to Person more simply, we can reference the constructor of Person:

java
// Referencing constructor
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Bob", "Alice", "Tim");
        List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
        System.out.println(persons);
    }
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
    public String toString() {
        return "Person: " + this.name;
    }
}

We will discuss the map() method of the Stream API later. Here, we observe that the map() method requires a FunctionalInterface definition as follows:

java
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

By matching the generics, we find that the method signature corresponds to Person apply(String), meaning it takes a String parameter and returns a Person. The constructor of the Person class matches this condition because its parameter is a String, and while the constructor does not have a return statement, it implicitly returns the instance (this) of type Person. Hence, we can reference the constructor using the syntax ClassName::new, allowing us to pass Person::new.

Exercise

Implement case-insensitive sorting using method references.

Summary

The FunctionalInterface allows passing:

  • Implementations of the interface (traditional approach, more verbose).
  • Lambda expressions (only need to list parameter names, with types inferred by the compiler).
  • Static methods that match the method signature.
  • Instance methods that match the method signature (the instance type is treated as the first parameter type).
  • Constructor methods that match the method signature (the instance type is treated as the return type).

The FunctionalInterface does not require inheritance, nor does it require method names to be the same; it only requires that the method parameter types and return type match, which is sufficient to consider the method signatures the same.

Method References has loaded