Java generics infers Object for nested method call instead of T if nested method argument is not typesafe

845 views Asked by At

today I have ran into strange javac behavior regarding generic type inference. Here's example class to ilustrate this strange behavior:

import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
public class Test {

    protected <T> T strange(T t, Map<String, String> map) {
        return t;
    }

    protected void ok(Map<String, String> map) {}
    protected <T> T test(T t) {
        T res = strange(t , new HashMap<String, String>());
        //Doesn't work
        //res = strange(t, new <String, String>HashMap());
        ok(new <String, String>HashMap());
        res = strange(t, Collections.<String, String>emptyMap());
        //Doesn't work
        //res = strange(t, Collections.EMPTY_MAP);
        res = strange(t, (Map<String, String>) Collections.EMPTY_MAP);
        ok(Collections.EMPTY_MAP);
        return res;
    }
}

Notice //Doesn't work comments. If you uncomment this code you will get strange compiler error:

Test.java:18: error: incompatible types
        res = strange(t, Collections.EMPTY_MAP);
                     ^
  required: T
  found:    Object
  where T is a type-variable:
    T extends Object declared in method <T>test(T)

Strange thing is that error complains about strange method return type is Object and not T, but when second unsafe parameter is cast to correct type then T is inferred correctly.

Can someone explain if this is correct behavior? Because it looks strange to me. Can it be a compiler bug?

Why this line works

T res = strange(t , new HashMap<String, String>());

and this one is doesn't?

T res = strange(t, new <String, String>HashMap());

I have tested it with java 7 and 6.

1

There are 1 answers

2
Sotirios Delimanolis On

The root cause is the use of Raw Types.

When you do this

new <String, String>HashMap()

The type arguments <String, String> are constructor type arguments, not class arguments. You can very well do

new <String, String, String, String, Integer, Foo, Bar, String>HashMap()

since the HashMap constructor does not declare any type parameters.

However, because you haven't provided the class type arguments, you're effectively using a raw type. The Java Language Specification says this about raw types

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

The basic idea is that if you use a raw type with some generic method, the generic types of the method are erased. So using

res = strange(t, new <String, String>HashMap());

makes your method look like

protected Object strange(Object t, Map map) {
    return t;
}

to the compiler.

and you are trying to do

/* T */ res = strange(t, new <String, String>HashMap());

You can't assign Object to a reference of type T because you don't know what T is. The compiler therefore throws an exception.

Related