2

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);
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
J John
  • 299
  • 1
  • 3
  • 15

3 Answers3

3

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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Zabuzard
  • 25,064
  • 8
  • 58
  • 82
1

Method reference quite intelligent feature in Java. So, when you use non-static method reference like String:toUpperCase Java automatically comes to know that it needs to call toUpperCase on the on the first parameter.Suppose there is two parameter a lambda expression expect then the method will call on the first parameter and the second parameter will pass as an argument of the method. Let' take an example.

List<String> empNames = Arrays.asList("Tom","Bob");
String s1 = empNames.stream().reduce("",String::concat); //line -1
String s2 = empNames.stream().reduce("",(a,b)->a.concat(b)); // line -2
System.out.println(s1);
System.out.println(s2);

So, on above example on line -1, String#concat method will call on the first parameter (that is a line-2) and a second parameter (that b for line -2) will pass as the argument.

It is possible for the multiple arguments (more than 2) method also but you need to very careful about the which sequence of the parameters.

Amit Bera
  • 7,075
  • 1
  • 19
  • 42
0

I highly recommend you to read the Oracle's article about method references: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

That is the form of a lambda expression:

s->s.toUpperCase()

And that is a method reference:

String::toUpperCase

Semantically, the method reference is the same as the lambda expression, it just has different syntax.

Morzel
  • 275
  • 2
  • 7