javalambdamethod-reference

Method reference - Difference between "Reference to a static method" and "Reference to an instance method of an arbitrary object of a particular type"


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);

Solution

  • Explanation

    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.


    Insights, Examples

    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> { ... }
    

    Note on 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.