Why won't this generic java code compile?
Asked Answered
G

4

35

In this simplified example I have a generic class, and a method that returns a Map regardless of the type parameter. Why does the compiler wipe out the types on the map when I don't specify a type on the containing class?

import java.util.Map;

public class MyClass<T>
{
    public Map<String, String> getMap()
    {   
        return null;
    }

    public void test()
    {   
        MyClass<Object> success = new MyClass<Object>();
        String s = success.getMap().get("");

        MyClass unchecked = new MyClass();
        Map<String, String> map = unchecked.getMap();  // Unchecked warning, why?
        String s2 = map.get("");

        MyClass fail = new MyClass();
        String s3 = fail.getMap().get("");  // Compiler error, why?
    }
}

I get this compiler error.

MyClass.java:20: incompatible types
found   : java.lang.Object
required: java.lang.String
                String s3 = fail.getMap().get("");  // Compiler error
Glary answered 19/3, 2009 at 13:27 Comment(2)
What is the exact text of the unchecked warning?Gazzo
warning: [unchecked] unchecked conversionGlary
S
38

Got it. This actually isn't a bug, strange as it might seem.

From section 4.8 (raw types) of the JLS:

The type of a constructor (§8.8), instance method (§8.8, §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 erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

So even though the method's type signature doesn't use any type parameters of the class itself, type erasure kicks in and the signature becomes effectively

public Map getMap()

In other words, I think you can imagine a raw type as being the same API as the generic type but with all <X> bits removed from everywhere (in the API, not the implementation).

EDIT: This code:

MyClass unchecked = new MyClass();
Map<String, String> map = unchecked.getMap();  // Unchecked warning, why?
String s2 = map.get("");

compiles because there's an implicit but unchecked conversion from the raw Map type to Map<String, String>. You can get the same effect by making an explicit conversion (which does nothing at execution time) in the last case:

// Compiles, but with an unchecked warning
String x = ((Map<String, String>)fail.getMap()).get("");
Scissure answered 19/3, 2009 at 13:44 Comment(1)
Hm ... it kind of makes sense ... but it's incredibly stupid at the same time ...Soniferous
S
5

Hm ... unfortunately I can't tell you why it fails. But I can give you a simple workaround:

Change the type of fail to MyClass<?>, then it will compile just fine.

Soniferous answered 19/3, 2009 at 13:32 Comment(3)
A cast works just fine too. Why would a type of ? affect the type of the map? It still makes no sense to me.Glary
@Motlin: Jon explained it pretty well: MyClass<?> is not a raw type.Soniferous
Now that I understand why your workaround works, I'm going to use it instead of a cast. Thanks.Glary
B
3

Very interesting question, and very interesting answer by Jon Skeet.

I just want to add something about the stupidity or not stupidity of this behaviour of the java compiler.

I think that the compiler assumes that if you don't specify the type parameter in a generc class you are not able (or don't want to) use any type parameter at all. You could use a version of java earlier than 5, or love to make casts manually.

It doesn't seem so stupid to me.

Bedazzle answered 19/3, 2009 at 14:47 Comment(0)
F
1

Generic types get erased after compiling.

When you do:

Map<String, String> map = unchecked.getMap();

you're forcing a cast from Map to Map<String, String>, and that's why the unchecked warning. However, after that you can do:

String s2 = map.get("");

because map is of type Map<String, String>.

However, when you do

String s3 = fail.getMap().get(""); 

you're not casting fail.getMap() to anything, so it's considered to be plainly Map, not Map<String, String>.

What you should do in the latter is something like:

String s3 = ((Map<String, String>fail.getMap()).get("");

which will still thrown a warning but will work anyway.

Furriery answered 19/3, 2009 at 13:46 Comment(1)
Then why does the first one compile? "Erasure" isn't the full answer here.Glary

© 2022 - 2024 — McMap. All rights reserved.