I've learned that there are 4 kinds of types in method reference. But I don't understand the difference between "Reference to a static method" and "Reference to an instance method of an arbitrary object of a particular type".
For example:
List<String> weeks = new ArrayList<>();
weeks.add("Monday");
weeks.add("Tuesday");
weeks.add("Wednesday");
weeks.add("Thursday");
weeks.add("Friday");
weeks.add("Saturday");
weeks.add("Sunday");
weeks.stream().map(String::toUpperCase).forEach(System.out::println);
The method toUpperCase
is not a static
method... so why can one write in the way above, rather than needing to use it this way:
weeks.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
The method
toUpperCase
is not a static method... so why can one write in the way above, rather than needing to use it this way:weeks.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
Method references are not limited to static
methods. Take a look at
.map(String::toUpperCase)
it is equivalent to
.map(s -> s.toUpperCase())
Java will just call the method you have referenced on the elements in the stream. In fact, this is the whole point of references.
The official Oracle tutorial explains this in more detail.
The method Stream#map
(documentation) has the following signature:
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
So it expects some Function
. In your case this is a Function<String, String>
which takes a String
, applies some method on it and then returns a String
.
Now we take a look at Function
(documentation). It has the following method:
R apply(T t)
Applies this function to the given argument.
This is exactly what you are providing with your method reference. You provide a Function<String, String>
that applies the given method reference on all objects. Your apply
would look like:
String apply(String t) {
return t.toUpperCase();
}
And the Lambda expression
.map(s -> s.toUpperCase())
generates the exact same Function
with the same apply
method.
So what you could do is
Function<String, String> toUpper1 = String::toUpperCase;
Function<String, String> toUpper2 = s -> s.toUpperCase();
System.out.println(toUpper1.apply("test"));
System.out.println(toUpper2.apply("test"));
And they will both output "TEST"
, they behave the same.
More details on this can be found in the Java Language Specification JLS§15.13. Especially take a look at the examples in the end of the chapter.
Another note, why does Java even know that String::toUpperCase
should be interpreted as Function<String, String>
? Well, in general it does not. That's why we always need to clearly specify the type:
// The left side of the statement makes it clear to the compiler
Function<String, String> toUpper1 = String::toUpperCase;
// The signature of the 'map' method makes it clear to the compiler
.map(String::toUpperCase)
Also note that we can only do such stuff with functional interfaces:
@FunctionalInterface
public interface Function<T, R> { ... }
System.out::println
For some reason you are not confused by
.forEach(System.out::println);
This method is not static
either.
The out
is an ordinary object instance and the println
is a non static
method of the PrintStream
(documentation) class. See System#out for the objects documentation.