Method References
As you know, lambda expressions allow you to use code as data and pass it as a method’s arguments. Another way to do it is to use method references. They are often even more readable than corresponding lambda expressions. Besides it, method references force developers to decompose a program into a set of short methods with clear areas of responsibility.
Make code clearer with method references
By method reference, we mean a function that refers to a particular method via its name and can be invoked any time we need it. The base syntax of a method reference looks like this:
objectOrClass :: methodName
where objectOrClass
can be a class name or a particular instance of a class.
Here is an example, we create a reference to the standard static method max
of the Integer
class.
BiFunction<Integer, Integer, Integer> max = Integer::max;
Here, Integer::max
is a method reference to a static method.
This code works because the definition of the method int max(int a, int b)
fits the type BiFunction<Integer, Integer, Integer>
: they both mean taking two integer arguments and returning an integer value.
You may already know that
BiFunction<Integer, Integer, Integer>
is not the only way to create a suitable object in this case. It is possible to useIntBinaryOperator
and some other classes. We will consider them in other topics.
Now we have the max
object which can be used as a function by invoking the apply
method. Let’s invoke it!
System.out.println(max.apply(50, 70)); // 70
So, once assigned to an object, a method reference works in the same way as a lambda expression.
Here is an alternative way to create the same object using a lambda expression:
BiFunction<Integer, Integer, Integer> max = (x, y) -> Integer.max(x, y);
It is recommended to use method references rather than lambda expressions whenever possible. Your code will be shorter, more readable and easier to test.
Note, we can refer both standard and our custom methods using method references.
Kinds of method references
It’s possible to write method references to both static and instance (non-static) methods.
In general, there are four kinds of method references:
- reference to a static method;
- reference to an instance method of an existing object;
- reference to an instance method of an object of a particular type;
- reference to a constructor.
- Reference to a static method
The general form is the following:
ClassName :: staticMethodName
Let’s take a look at the reference to the static method sqrt
of the class Math
:
Function<Double, Double> sqrt = Math::sqrt;
Now we can invoke the sqrt
method for double
values:
sqrt.apply(100.0d); // the result is 10.0d
The sqrt
method can be also written using the following lambda expression:
Function<Double, Double> sqrt = x -> Math.sqrt(x);
- Reference to an instance method of an object
The general form looks like this:
objectName :: instanceMethodName
Let’s check out the example of a reference to the indexOf
method of a particular string.
String whatsGoingOnText = "What's going on here?";
Function<String, Integer> indexWithinWhatsGoingOnText = whatsGoingOnText::indexOf;
Here is the result of applying it to different arguments:
System.out.println(indexWithinWhatsGoingOnText.apply("going")); // 7
System.out.println(indexWithinWhatsGoingOnText.apply("Hi")); // -1
As you can see, actually we always work with the whatsGoingOnText
object captured from the context.
The following example of a lambda expression is fully equivalent of this one and can make your understanding of the situation better.
Function<String, Integer> indexWithinWhatsGoingOnText = string -> whatsGoingOnText.indexOf(string);
- Reference to an instance method of an object of a particular type
Here is a general form of a reference:
ClassName :: instanceMethodName
Let’s focus on the following reference to an instance the method doubleValue
of the class Long
:
Function<Long, Double> converter = Long::doubleValue;
Now we can invoke the converter for long
values:
converter.apply(100L); // the result is 100.0d
converter.apply(200L); // the result is 200.0d
Also, we can write the same converter using the following lambda expression:
Function<Long, Double> converter = val -> val.doubleValue();
- Reference to a constructor
This reference has the following declaration:
ClassName :: new
For example, let’s consider our custom class Person
with a single field name.
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
Here is a reference to the constructor of this class:
Function<String, Person> personGenerator = Person::new;
This function produces new Person
objects based on their names.
Person johnFoster = personGenerator.apply("John Foster"); // we have a John Foster object
Here is the corresponding lambda expression that does the same.
Function<String, Person> personGenerator = name -> new Person(name);
Conclusion
You’ve learned a new way to create function objects using method references. It has much in common with lambda expressions, but allows writing more readable and decomposed code. At the end of this topic, you should memorize all four types of method references and be ready to use them in your programs when you need to convey a piece of code into some method.