Java 8: An Introduction to Method References

Here, we’ll explore the new “::” operator which allows you to reference methods in place of Lambda expressions to make your stream processing more streamlined.

13 Andrew Willis

Method References

In some of our previous posts we saw how Streams and Lambda Expressions could be used together in order to process data in more clear and concise ways.

Lambda Expressions are great but sometimes it becomes a bit cumbersome to write out the same piece of code just to keep calling a specific method that already exists.

Let’s use a simple User object from our Streams blog post to demonstrate this which contains a name (String), date of birth (LocalDate) & active (boolean) status. We’ll leave out the class file definition for brevity & you can assume that a no-argument constructor, a constructor that sets all fields, and normal get & set methods are present.

This is our list of users that we’ll be working with:

List<User> users = Arrays.asList(
new User("Karen",    LocalDate.of(2000, 10, 31),    true),
new User("Gina",     LocalDate.of(1981, 8, 8),      true),
new User("Nigel",    LocalDate.of(1978, 9, 1),      false),
new User("Steve",    LocalDate.of(1994, 12, 19),    false),
new User("Geoff",    LocalDate.of(1954, 2, 27),     true)
);

How could we filter our users so that we only get the names of all active users using Lambda Expressions? Easy:

List<String> activeUserNames = users
.stream()
.filter(user -> user.isActive())
.map(user -> user.getName())
.collect(Collectors.toList());

That works fine & does the job but is there an easier way to do this? This is where method references come in. Using these we can rewrite the above to be:

List<String> activeUserNames = users
.stream()
.filter(User::isActive)
.map(User::getName)
.collect(Collectors.toList());

Here, we’ve told Java to use specific methods from the User class for the filter & map methods on each user object in the stream by using a new syntax, “<Class/object>::<methodName>”.

Basically, this is just shorthand syntax for a Lambda Expression but this version is more concise & readable. A further benefit is that it can also promote code reusability. For example, if you find yourself using the same Lambda Expression multiple times it would make sense to create a method to do the same work & then reference that instead.

When can you use method references?

You can use method references anywhere you can use Lambda Expressions, which means that a Functional Interface is needed, but only if the Lambda Expression would invoke a single, already defined, method & do nothing else.

The method signature must also match that of the Functional Interface being used. For example, when used in conjunction with a Predicate, which requires a single input argument & returns a boolean value, your method would need to accept an argument & return a boolean.

But what about our example above where “User::isActive” doesn’t accept an argument? In this scenario, the user object in the stream is treated as the input argument & the return value from the “isActive” method invoked on that object is treated as the return value.

You won’t be able to use method references if you need to invoke more than one method within a Lambda Expression or if you need to pass extra arguments into the method.

What kinds of methods can you reference?

There are 4 different kinds of methods that we can reference using this syntax:

  • Static methods, e.g. ClassName::staticMethod
  • Instance methods of an object of a particular type, e.g. ClassName::instanceMethod
  • Instance method of a particular object instance, e.g. object::instanceMethod
  • Constructors, e.g. ClassName::new

Let’s look at an example of each of these together with their equivalent Lambda Expression versions.

Static methods:

Function<String, Integer> staticStringToInteger1 = string -> Integer.parseInt(string);
Function<String, Integer> staticStringToInteger2 = Integer::parseInt;

Instance methods of an object of a particular type:

Function<String, String> anyInstanceOfStringToUpperCase1 = string -> string.toUpperCase();
Function<String, String> anyInstanceOfStringToUpperCase2 = String::toUpperCase;

Instance method of a particular object instance:

String stringInstance = "Lorem Ipsum";
Function<Integer, String> instanceSubstring1 = idx -> stringInstance.substring(idx);
Function<Integer, String> instanceSubstring2 = stringInstance::substring;

Constructors:

Supplier<Object> constructor1 = () -> new Object();
Supplier<Object> constructor2 = Object::new;

Summary

From what we’ve seen above, using method references can make our code cleaner, more readable & promote code reusability.

As with Lambda Expressions, they are exceptionally useful when combined with Streams, amongst other things, as it makes it far quicker & easier to process your data.

For more information see the Java Tutorial on Method References.