Java 8 Method References Examples
Basic Example
Method references are probably most useful when used with the Stream
API to
apply a function to every element of a stream. For example we can use map()
to make each element of a Stream<String>
to upper case:
package xyz.byexample.java8;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Map {
public static void main(String[] args) {
List<String> letters = Arrays.asList("a", "b", "c");
List<String> upperCase = letters.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCase);
}
}
Output
[A, B, C]
map()
requires a function that fits the Function<T, R>
interface (i.e. a
function that accepts an argument and returns a result).
The String::toUpperCase
is a method reference to the String.toUpperCase()
method that happens fits the interface Function<T, R>
so we can use it here.
Constructor Example
You can use method references for an objects constructor too. A good example of where you’d need to use this would be when you need to return an array from a stream:
List<String> letters = Arrays.asList("a", "b", "c");
String[] lettersArray = letters.stream().toArray(String[]::new);
for (int i=0; i<lettersArray.length; i++) {
System.out.print(lettersArray[i] + ", ");
}
Output
a, b, c,
If the constructor takes two arguments, we’d need to use the BiFunction
interface - if you need more than two arguments you’ll need to create your own
functional interface for that.
Examples of how Method references actually work
You can think of method references as a simple shorthand for a lambda expression.
For example, in the case of a static method, we could use a lambda expression
like this to get a Consumer<String>
function that prints to the console:
Consumer<String> printToConsole = s -> System.out.println(s);
This can be simplified to the following method reference to get the same
Consumer<String>
function that prints to the console as well:
Consumer<String> printToConsole = System.out::println;
Both of these snippets are equivalent and produce the same output when executed.
printToConsole.accept("hello world");
Output
hello world
With the method reference approach the s
argument is no-longer required as
java will automatically pass the arguments to the method we’re referencing.
This also works in a similar way for instance methods as well - for example
the classic example of changing the case of a stream of strings
uses a method reference of String::toUpperCase()
.
Conceptually you can think of String::toUpperCase()
as equivalent to the
following lambda expression that calls the toUpperCase()
method on the
instance str
:
Function<String, String> toUpper = (str) -> str.toUpperCase();
System.out.println(toUpper.apply("a"));
Output
A
Method references allow us to replace the instance with the type of the instance we’re dealing with, and again java will automatically pass the arguments to the method.
Function<String, String> toUpper = String::toUpperCase;
System.out.println(toUpper.apply("a"));
Output
A
So going back to our map()
example of changing all elements of the stream to
upper case we can now see that the String::toUpperCase
satisfies the function
interface for map()
(i.e. it is a Function<T, R>
), and we now know that we
can use a method reference for the type of the instance and java will
automatically pass the arguments (i.e. each String
in the Stream<String>
)
to the method we’re referencing.
The same is true for constructors too:
Function<Integer, List> newList = (Integer s) -> new ArrayList(s);
System.out.println(newList.apply(10));
Output
[]
This is equivalent to
Function<Integer, List> newList = ArrayList::new;
System.out.println(newList.apply(10));
Output
[]