29

This code compiles in Java 8 but fails to compile in Java 7:

class Map<K,V> {
    static <K,V> Map<K,V> empty() {return null;}
    Map<K,V> put(K k, V v) {return null;}
    V get(K k) {return null;}
}

class A {
    static void f(Map<Integer,String> m){}
    public static void main(String[] args) {
        f(Map.empty());
    }
}

It doesn't infer the concrete type of the Map being returned from Map.empty():

$ javac7 A.java
A.java:10: error: method f in class A cannot be applied to given types;
        f(Map.empty());
        ^
  required: Map<Integer,String>
  found: Map<Object,Object>
  reason: actual argument Map<Object,Object> cannot be converted to Map<Integer,String> by method invocation conversion
1 error

It compiles if you change the f call to f(Map.<Integer,String>empty());. In Java 8, it works without having to resort to this.

But if you change the f call to f(Map.empty().put(1,"A").put(2,"B"));, it fails to compile once again, on both Java 7 and 8. Why?

$ $javac7 A.java 
A.java:10: error: method f in class A cannot be applied to given types;
        f(Map.empty().put(1,"A").put(2,"B"));
        ^
  required: Map<Integer,String>
  found: Map<Object,Object>
  reason: actual argument Map<Object,Object> cannot be converted to Map<Integer,String> by method invocation conversion
1 error

$ $javac8 A.java
A.java:10: error: incompatible types: Map<Object,Object> cannot be converted to Map<Integer,String>
        f(Map.empty().put(1,"A").put(2,"B"));
                                    ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

$ $javac8 -Xdiags:verbose A.java
A.java:10: error: method f in class A cannot be applied to given types;
        f(Map.empty().put(1,"A").put(2,"B"));
        ^
  required: Map<Integer,String>
  found: Map<Object,Object>
  reason: argument mismatch; Map<Object,Object> cannot be converted to Map<Integer,String>
1 error
Didier L
  • 13,411
  • 5
  • 46
  • 90
Dog
  • 7,347
  • 8
  • 35
  • 70
  • Just make it `static Map empty() {return null;}`, Its not like your adding any modifiers using the generics anyway. – ug_ Jul 17 '14 at 04:39

2 Answers2

33

Why ?

Because the type inference of generics types has not been expanded to chained invocation.

From the java tutorial on generics type inference:

The notion of what is a target type has been expanded to include method arguments.

That is why this code:

f(Map.empty());

compiles.

But this code doesn't because this is a chained invocation:

f(Map.empty().put(1,"A").put(2,"B"));

You can also find a small paragraph in the JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation (specifically part D):

There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived; but perhaps with additional enhancements it could be added in the future.

So maybe in Java 9.

River
  • 7,472
  • 11
  • 47
  • 61
gontard
  • 25,988
  • 10
  • 86
  • 116
0

What is the type of Map.Entry.comparingByValue().reversed()? - Just to answer this question simpler.

unSortedMap.entrySet().stream()
                    .filter(e -> e.getValue() > 1)
                    .sorted(Entry.comparingByValue(Comparator.reverseOrder()))
                    .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
                            (e1, e2) -> e1, LinkedHashMap::new));
Ram Govind
  • 11
  • 1