13
public class Test {
    public static void main(String[] args) {
        List<Pair<String, Integer>> list = new ArrayList<>();
        list.add(new Pair<>("1", 8));
        list.add(new Pair<>("3", 2));
        list.add(new Pair<>("2", 15));

        list.stream()
            .sorted(Comparator.comparingInt(p -> p.v))
            .map(p -> p.k)
            .forEach(System.out::println);

    }
}

class Pair<K, V> {
    K k;
    V v;
    public Pair(K k, V v) {
        this.k = k;
        this.v = v;
    }
}

Ok, as you understood this code is printing my pair keys from the lowest value associated to the highest so I get the expected output:

3 1 2

So far so good. Now I wanted to do the reverse, I thought I would only have to do

list.stream()
    .sorted(Comparator.comparingInt(p -> p.v).reversed())
    .map(p -> p.k)
    .forEach(System.out::println);

But I get a compilation error:

v cannot be resolved or is not a field

So it seems like comparingInt is returning a Comparator<Object>. Why it so? Shouldn't it return a Comparator<Integer> ?

These are both reproductible with Eclipse Luna version 1 and javac.

javac -version => 1.8.0
java  -version => java version "1.8.0_25"

Oh also feel free to change the title of my question is you find it too general, but I couldn't find the correct terms

user2336315
  • 15,697
  • 10
  • 46
  • 64

1 Answers1

10

I believe it's just that type inference is failing, basically - because the reverse() call gets in the way of the expected argument type of sorted() and the lambda expression.

You can do it if you specify the type to comparingInt explicitly:

list.stream()
    .sorted(Comparator.<Pair<String, Integer>>comparingInt(p -> p.v).reversed())
    .map(p -> p.k)
    .forEach(System.out::println);

Or if you just declare the comparer first:

Comparator<Pair<String, Integer>> forward = Comparator.comparingInt(p -> p.v);
list.stream()
    .sorted(forward.reversed())
    .map(p -> p.k)
    .forEach(System.out::println);

It feels to me like there should be a Stream.reverseSorted so make this sort of thing really easy, but it doesn't look like it exists :(

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • ... which is quite ugly, then I prefer use `sorted((p1, p2) -> p2.v.compareTo(p1.v))`. So it's likely a bug in the compiler? – user2336315 Nov 29 '14 at 18:16
  • 4
    @user2336315: I agree it's ugly - but that doesn't mean it's a bug in the compiler. I believe it's a limitation of the type inference in the language, which isn't the same thing. To be honest, I've never fully understood how generic type inference in Java works (and I believe it's changed in a few places over time) - but this definitely feels like a simple limitation to me. – Jon Skeet Nov 29 '14 at 18:16
  • Ah I see. Maybe in a future release, this will be improved. I know they have alreay done some improvments so that's why I found it was weird that it didn't compile... :( – user2336315 Nov 29 '14 at 18:17
  • As @RobertBain pointed in out it has already been answered here: http://stackoverflow.com/a/25173599/2336315. I'll accept your answer since it provides a decent workaround. – user2336315 Nov 29 '14 at 18:22
  • 1
    @user2336315: I'm glad Robert's answer was similar to mine :) – Jon Skeet Nov 29 '14 at 18:22
  • Well it's not his answer actually, but the answer + the comment of Brian Goetz provides a really nice explanation :-) – user2336315 Nov 29 '14 at 18:24
  • @user2336315 Actually, that other question even gives you a better workaround - using method reference instead of lambda. – Eran Nov 29 '14 at 18:28
  • @Eran Yes but is not always applicable, for example if you want to sort map entries. By that I mean, if you have a `Map` and you want to sort by the size of the list for example, its not possible with method reference. – user2336315 Nov 29 '14 at 18:33
  • 10
    Not a bug in the compiler. Type inference will never be perfect; it has intrinsic limitations, and the harder we push it, the more surprising the effects when its limits are exceeded. That said, I'll also point out that you can get the same effect with an explicitly typed lambda, which is slightly less clunky than explicit type witnesses in the generic method call. – Brian Goetz Nov 29 '14 at 19:28
  • 3
    See also http://stackoverflow.com/a/26883991/2711488 In other words, while type inference fails for method chaining it does work with nested method invocations. So instead of `Comparator.comparingInt(p -> p.v).reversed()` you can use `Collections.reverseOrder(Comparator.comparingInt(p -> p.v))` That’ll work… – Holger Dec 01 '14 at 10:43