dlamblin
dlamblin

Reputation: 45351

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))) {
            lines.parallel()
              .map(l -> l.toLowerCase().replaceAll("[^a-z\\s]", "").split("\\s"))
              .flatMap(Arrays::stream)
              .filter(s -> !s.isEmpty())
              .collect(Collectors.groupingBy(
                      Function.identity(), Collectors.counting()))
                // Sort Map<K,V> Entries by their Integer value descending
                .entrySet().parallelStream()
// MY QUESTION IS ABOUT THIS METHOD:
                  .sorted(
                    Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
// --------------------------------- //
                  .forEachOrdered(e -> out.printf("%5d\t%s\n", e.getValue(), e.getKey()));
        }
        out.close();
    }
}

So the article would suggest that the line:

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

could be written as:

.sorted(Comparator.comparing(Map.Entry::getValue).reversed())

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 comparing method signatures have the exact same first parameter and static scope, yet the former works while the latter complains about getValue being non-static.

My original thought was to write it as either:

.sorted(Map.Entry.comparingByValue())

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

.sorted(Map.Entry.comparingByValue().reversed())

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 Map.Entry::getValue form in my "could be written as" line though.

Upvotes: 3

Views: 853

Answers (1)

Radiodef
Radiodef

Reputation: 37855

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.

Upvotes: 3

Related Questions