dlamblin dlamblin - 1 year ago 132
Java Question

Why does stream sorted() have trouble inferring my type?

I was reading this article and tried counting some words in a text file and found I could not reverse sort similarly to how it showed in listing 1 of the article.

I have some code that works though:

public class WordCounter {
public static final PrintWriter out = new PrintWriter(System.out, true);

public static void main(String... args) throws IOException {
//The need to put "", in front of args in the next line is frustrating.
try (Stream<String> lines = Files.lines(Paths.get("", args))) {
.map(l -> l.toLowerCase().replaceAll("[^a-z\\s]", "").split("\\s"))
.filter(s -> !s.isEmpty())
Function.identity(), Collectors.counting()))
// Sort Map<K,V> Entries by their Integer value descending
Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
// --------------------------------- //
.forEachOrdered(e -> out.printf("%5d\t%s\n", e.getValue(), e.getKey()));

So the article would suggest that the line:

.sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))

could be written as:


For this though, the Java compiler complains that:

Error:(46, 49) java: invalid method reference non-static method
getValue() cannot be referenced from a static context

The two
method signatures have the exact same first parameter and static scope, yet the former works while the latter complains about
being non-static.

My original thought was to write it as either:


Which compiles and runs but is not reversed. Or as:


Which again doesn't compile, giving an error message of:

Error:(48, 62) java: incompatible types: java.util.Comparator<java.util.Map.Entry<java.lang.Object,V>> cannot be converted to java.util.Comparator<? super java.util.Map.Entry<java.lang.String,java.lang.Long>>

Okay, so, that should be:

.sorted(Map.Entry.<String, Long>comparingByValue().reversed())

Which works.

I can't seem to see how to give a similar generic type specification to the
form in my "could be written as" line though.

Answer Source

As to why this happens: while type inference has come leaps and bounds in Java 8, it will still only use the return target type if the return value is assigned to something.

In Java 7 we were only able to use this in an assignment context (using =) and it was a little bit clunky. In Java 8, it's less clunky and we can use it in invocation contexts (passed as a method argument, which assigns it to the formal parameter).

So the way I understand it, if the method invocation isn't used in an assignment context or invocation context, target type inference simply turns off, because it's no longer something called a poly expression (15.12, 18.5.2). So says the JLS.

In short, target type inference only works if the return value is:

  • assigned directly to a variable using =, as in v = foo();.
  • passed directly to a method, as in bar(foo()).

Once you chain a method call in, like v = foo().zap(), it stops working.

Lifted from my comment:

I can't seem to see how to give a similar generic type specification to the Map.Entry::getValue form though.

This would be Map.Entry<String, Long>::getValue.