Method References in Java
As you know, lambda expressions in Java programming 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, method references encourage a functional programming style by forcing 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
Here, 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 static method reference .
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 use IntBinaryOperator
and some other classes. We will consider them in other topics.
Now we have the max
object that 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 if you just need to invoke a standard method without other operations. Your code will be shorter, more readable, and easier to test.
Note that in Java we can refer to 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.
1) Reference to a static method
The general form looks like this:
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 also be written using the following type of lambda expression:
Function<Double, Double> sqrt = x -> Math.sqrt(x);
2) 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 the function 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 code snippet of a lambda expression is a full equivalent of the reference above and can make your understanding of the situation better:
Function<String, Integer> indexWithinWhatsGoingOnText = string -> whatsGoingOnText.indexOf(string);
3) Reference to an instance method of an object of a particular type
Here is a general form of a reference:
ClassName :: instanceMethodName
In this case, you need to pass an instance of the class as the function argument.
Let's focus on the following reference to an instance of the doubleValue
method of the Long
class:
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();
4) Reference to a constructor
This reference has the following declaration:
ClassName :: new
For example, let's consider our custom Person
class with a single field name
.
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
Here is the constructor reference 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);
Further, we will use lambda expressions and method references together.
More practical examples of this feature will be explored in the following topics. For now, it is enough to grasp the general idea and the syntax of method references.
Conclusion
You've learned a new approach to create function objects by using method references. It has much in common with lambda expressions but allows writing decomposed, cleaner, and more expressive code, while also improving readability. 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.