Understanding Method References in Java9 min read
Before Java 8, to provide an implementation for an interface, we need either to create a concrete class that implements this interface or, more concisely we can, use an anonymous class for this purpose, you can either utilize one of these approaches depending on the context, and they are viable solutions when comes in term of an interface with multiple methods need implementing. But what about the interface with only one method? Most of us might feel uneased when typing some boilerplate code to implement just one method of an interface. To make the Java language itself less verbose and to increase its expressiveness, Java designers have introduced some concepts such as “lambda expression” and “functional interface” to address this problematic situation. For example, we have an interface with a single method named compute()
which takes two parameters and returns their sum, before Java 8, we would typically do:
interface Sum {
int compute(int x, int y);
}
class SumImpl implements Sum {
@Override
public int compute(int x, int y) {
return x + y;
}
}
class Main {
public static void main(String[] args) {
SumImpl sum = new SumImpl();
sum.compute(10, 5); // 15
}
}
Or, more concisely, with anonymous class:
interface Sum {
int compute(int x, int y);
}
class Main {
Sum sum = new Sum() {
@Override
public int compute(int x, int y) {
return x + y;
}
};
sum.compute(10, 5); // 15
}
From anonymous classes to lambdas
But if you use a modern IDE such as IntelliJ IDEA, it will suggest you replace the anonymous with a lambda, which turns out to be much more concise:
Sum sum = (a, b) -> a + b;
sum.compute(10, 5); // 15
Functions with only one abstract method are known as functional interfaces, and their instances are called functional objects. We can take advantage of lambda expressions to create functional objects instead of a traditional anonymous class.
Before we dive into using method reference, let’s make one more concrete lambda example. We have a simple Person
class, which contains a static method compareByNameThenAge
:
class Person {
private String name;
private int age;
public static int compareByNameThenAge(Person a, Person b) {
int result = a.getName().compareTo(b.getName());
if (result != 0) {
return result;
} else {
return Integer.compare(a.getAge(), b.getAge());
}
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters, setters and toString method.
}
The compareByNameThenAge
will be used as our custom sorting function, it first compares the name of 2 objects, if they are the same, then their age will be compared. Now, we create a list of Person
, and some Person data to it, and then output the result after sorting the list:
List<Person> people = new ArrayList<>();
people.add(new Person("Nam Do", 21));
people.add(new Person("Ethan", 23));
people.add(new Person("Ethan", 21));
people.add(new Person("Nam Do", 25));
people.sort((Person a, Person b) -> Person.compareByNameThenAge(a, b));
for (Person person : people) {
System.out.println(person);
}
The sort()
method of an Array List
instance has the following signature:
public void sort(Comparator<? super E> c)
And if we go deeper to see what’s inside Comparator
, we’ll see inside this interface there is only one abstract method compareTo
as the following:
int compare(T o1, T o2);
That’s why we can use the lambda expression when we sort our list, and here is the final output:
Person{name='Ethan', age=21}
Person{name='Ethan', age=23}
Person{name='Nam Do', age=21}
Person{name='Nam Do', age=25}
From lambdas to method references
Our list is sorted nicely and the lambda expression is cool, however, did you notice that in this example above, the lambda expression did nothing more than call an existing method, compareByNameThenAge
in this case.
In this circumstance, method reference is a better approach for calling an existing function, because it’s more concise than lambda expression, and by referring to a function, your code can be more intuitive:
people.sort(Person::compareByNameThenAge);
Both the lambda expression above and this method reference are semantically the same, the former parameter list is copied from Comparator<Person>.compare
, which is (Person, Person)
, and the body it calls the compareByNameThenAge
method from the Person
class.
Our job is done more nicely and clearly with method references, and in most cases, you should prefer method references to lambdas. There is only one little caveat: method references can only be used to replace lambdas, and the method you want to refer to already exists. As in our example, our method reference refers to a static method compareByNameThenAge
which exists in the Person
class, and it worked because it takes 2 parameters as the compareTo
method of the Comparator
interface and they return the same type, in this case int
, if you try to construct the compareByNameThenAge
method with 3 parameters instead of 2, or return the long value instead of an integer, the code will not compile.
Method references are also used for class and array creation, which is summarized in the table below:
Method reference type | Syntax | Example |
Static | ContainingClass:staticMethod | Person::compareByNameThenAge |
Instance method (bound receiver) | ObjectReference::instanceMethod | Instant.now()::isBefore |
Instance method (unbound receiver) | ClassName::instanceMethod | String::toLowerCase |
Constructor | ClassName::new | HashSet::new |
Static method reference
We already did an example of the static method reference, in which our function object refers to the static method compareByNameThenAge
of the Person
class, is pretty straightforward and I think we can move on to another method reference type for now.
Bound method reference (bound receiver)
Inbound method reference, the referenced method is called upon an instance of a particular class, for example:
Instant.now()::isBefore
The isBefore
method is invoked after an instance of the Instant
class is created.
Here is another example:
String hello = "learntocodetogether.com";
Supplier<String> supplier = hello::toUpperCase;
System.out.println(supplier.get()); // LEARNTOCODETOGETHER.COM
The Supplier
is a functional interface that lies in the java.util.function
package, this interface has one abstract method T get()
which receives no argument and returns something. In the second line, we call toUpperCase
method on an instance of the String class, which is hello
.
Unbound method reference (unbound receiver)
For the unbound method reference, we call an instance method by using the class name of this instance, and the call to this method is unbounded from any instance, for example:
String::toUpperCase
In this case, we no need to create an instance of the String
class to work with the toUpperCase
method, actually, it won’t make much sense if I just put the code like this, as you might guess there should be one instance of the String class that needs referring in order to make it “upper case”, yeah, of course, it has to. For instance:
String hello = "learntocodetogether.com";
UnaryOperator<String> unaryOperator = String::toUpperCase;
System.out.println(unaryOperator.apply(hello)); // LEARNTOCODETOGETHER.COM
Here we use another functional interface, the apply
method of the UnaryOperator
interface is inherited from its parent Function
interface. On the second line, we use the unbound method reference in which we call the toUpperCase
method right on the String class, on the third line, we pass an instance of the String class, and then the toUpperCase
method will be invoked on this particular instance, resulting in a capitalized string as we expected.
Of course, as a simple example, we can just use Function
instead, but the rule is functional interfaces should be as specialized as possible, so.
Constructor method reference
You can also create a new instance of a class with the help of method references, moving back to our Person
example, as we keep adding more and more people to our list, there is a high chance that our list will contain some duplicated elements. We can still print the result after sorting the list with some identical personal information, or we could turn our list into a set to get all distinct elements by using method reference.
To do this, let’s first add some more elements to our list:
people.add(new Person("Nam Do", 21));
people.add(new Person("Ethan", 23));
people.add(new Person("Rick Sanchez", 70));
people.add(new Person("Morty Smith", 14));
After sorting the list, here is what we get:
Person{name='Ethan', age=21}
Person{name='Ethan', age=23}
Person{name='Ethan', age=23}
Person{name='Morty Smith', age=14}
Person{name='Nam Do', age=21}
Person{name='Nam Do', age=21}
Person{name='Nam Do', age=25}
Person{name='Rick Sanchez', age=70}
A lot of duplication here, so let’s create a Set, first of all, we need to override both equals
and hashCode
method in the Person
class:
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
return getAge() == person.getAge() &&
Objects.equals(getName(), person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
You need to override hashCode
method whenever you override equals
, it’s the rule. And our Set will only function properly if we override both these two functions correctly. For now, we can create a set that contains elements that we already pass in our people
list:
Set<Person> personSet = new HashSet<>(people); // now all duplicated elements are gone.
Nonetheless, this article is about method references, and it’s not about passing a list to a Set constructor. We’re interested in how to create an instance of a class with a method reference. To make this more concrete, let’s transform our list to whatever Collection type, e.g. a Linked List, a HashMap, a HashSet, etc…First, we need to create a utility method to transform one collection type into another:
public static <T, S extends Collection<T>, D extends Collection<T>>
D transformStoD(S source, Supplier<D> supplier) {
D destinationResult = supplier.get();
destinationResult.addAll(source);
return destinationResult;
}
We know the basic motive of this method; however, since this method contains some level of generic code, let me explain it a little bit:
- On the method signature, the type
T
acts as a placeholder for elements inside the Collection,S, D extends Collection<T>
means thatS, or D
types need to be either the Collection type or the sub-type of it. - The first parameter is for our source data structure, and the second parameter is for our desired data structure after transforming.
- On the first line of this method, we call the
get
method onSupplier
object to get our desired type. - Then we add everything from the source to our destination and return the result.
Finally, we create a Set instance with the help of a method reference:
Set<Person> personSet = Person.transformStoD(people, HashSet<Person>::new);
personSet.forEach(System.out::println); // method reference on static method
/* OUTPUT:
Person{name='Ethan', age=21}
Person{name='Nam Do', age=25}
Person{name='Ethan', age=23}
Person{name='Rick Sanchez', age=70}
Person{name='Nam Do', age=21}
Person{name='Morty Smith', age=14}
*/
You can also transform your collection to another type, such as a linked list:
List<Person> personLinkedList = Person.transformStoD(people, LinkedList<Person>::new);