5
public static void main(String o[]) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.entrySet().stream().sorted(Comparator.comparing(Entry::getValue)).forEach(System.out::println);
}

Above code builds and runs perfectly but it shouldn't. Comparator.comparing takes a function reference and only those methods which takes one argument and returns one argument can be mapped on this. But in above code getValue is mapped and works fine but it doesn't take any parameter. Code should give build issue but doesn't. Is there any issue with my concept?

Holger
  • 285,553
  • 42
  • 434
  • 765
Umair Zahid
  • 135
  • 9
  • 2
    See [Difference between method reference Bound Receiver and UnBound Receiver](https://stackoverflow.com/q/35914775/2711488) – Holger Sep 05 '17 at 10:24
  • 1
    Or simply said, `Entry::getValue` is the same as `entry -> entry.getValue()`. But you can replace `Comparator.comparing(Entry::getValue)` with `Entry.comparingByValue()`, by the way. – Holger Sep 05 '17 at 10:27

2 Answers2

5

The single argument comparing method:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)

takes a Function<? super T, ? extends U> argument, which is a functional interface that contains a single method that takes a argument of one type and returns a value of some other type.

Entry::getValue takes an argument of one type (Map.Entry<String, Integer> in your example) and returns a value of some other type (Integer in your example). Therefore it matches the Function functional interface.

But in above code getValue is mapped and works fine but it doesn't take any parameter.

Yes it does - each Map.Entry element taken from the Stream serves as an argument of the apply() method of the Function interface.

Perhaps this will clarify:

Function<Map.Entry<Integer, String>, String> func = Map.Entry::getValue;

The getValue() method of Map.Entry can be viewed as a Function that accepts a Map.Entry instance and return the value of that instance (returned by calling getValue() on that instance).

Eran
  • 387,369
  • 54
  • 702
  • 768
  • I can't see any method in import java.util.Map.Entry class with name "getValue" which takes an argument, but it still works fine. – Umair Zahid Sep 05 '17 at 10:20
  • 2
    @UmairZahid The argument is the `Map.Entry` instance on which `getValue()` is executed. The `getValue()` method doesn't have an argument. – Eran Sep 05 '17 at 10:22
2

In this case, your method reference is an instance method, rather than a static one, so instead of for each item calling and using the value of:

Entry.getValue(item)

It uses

item.getValue() [JavaDoc] (https://docs.oracle.com/javase/7/docs/api/java/util/Map.Entry.html#getValue())

so it acts as the lambda: p -> p.getValue(), converting an entry into its value for comparison, as it has one argument and returns one value without throwing exceptions it can implement Function<Entry<String, Integer>, Integer>.

If both a static and instance method exists you cannot use a method reference, as per this question: Link

Worked example

jrtapsell
  • 6,719
  • 1
  • 26
  • 49
  • Even if it uses instance for calling method, method signature should be the same. I don't think it omits argument at runtime. WDYT? – Umair Zahid Sep 05 '17 at 10:23
  • Static method references are called with the instance, whereas instance method references are called on the instance – jrtapsell Sep 05 '17 at 10:24
  • Agreed but the implementation can change based on instance or static cases, method signature remains same. – Umair Zahid Sep 05 '17 at 10:27
  • I have added a link to a worked example, both reference types have the same syntax, but only the static one has an argument – jrtapsell Sep 05 '17 at 10:29