10

I have the following code:

public class Main {
    public static void main(String[] args) {
        Generic generic = new Generic<Integer>(5);
        List<String> stringList = generic.getStringList(); // this line is where the compiler complains
    }
}

public class Generic<T> {
    private T member;

    public Generic(T member) {
        this.member = member;
    }

    public T getMember() {
        return member;
    }

    public List<String> getStringList() {
        return new ArrayList<String>();
    }
}

Note that class Generic is declared with a generic type parameter, but the variable generic in method main is of the erasure type, i. e. without a type parameter. What I do not understand is why the compiler complains about the line with the assignment of the List<String>:

Warning:(6, 56) java: unchecked conversion
  required: java.util.List<java.lang.String>
  found:    java.util.List

The Method clearly returns a List<String>, independently of the generic parameter of the class. And this is what the variable stringList expects. It seems that not using the generic parameter on class level for variable generic switches off all generics processing, and not just that depending on the type parameter of the class.

I am using the standard Oracle Java 1.7.0_55 compiler, if that matters.

I am not asking how to get rid of the warning. I know I should declare the variable type as Generic<Integer>, or could use @SuppressWarnings("unchecked"). My questions are the following:

Is this behavior documented?

What is the reason for this strange behavior?

FrankPl
  • 13,205
  • 2
  • 14
  • 40

3 Answers3

13

When you use the erasure of a type, it removes all trace of generics - not just the uses of the type parameter T. So your generic variable acts as if it's referring to this type:

// After type erasure
public class Generic {
    private Object member;

    public Generic(Object member) {
        this.member = member;
    }

    public Object getMember() {
        return member;
    }

    public List getStringList() {
        return new ArrayList();
    }
}

This is documented in the JLS - start at section 4.6 and follow the links. It's not as clear as it might be, but it is documented.

The reasoning is that if you're using a raw type, the compiler expects you to not be aware of generics at all - because it's likely to be compiling legacy pre-Java-5 code. That's proved a little unrealistic over time, but I believe it was the motivation for the spec being the way it is.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I think especially the sentence "Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables." of the cited document means that all type parameters are removed from all methods, independently if they are linked to the type parameters of the class or not. – FrankPl Oct 24 '14 at 16:29
4

The answer to your first question is here.

Specifically the lines:

Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

The type parameters of a constructor or method (§8.4.4), and the return type (§8.4.5) of a method, also undergo erasure if the constructor or method's signature is erased.

The erasure of the signature of a generic method has no type parameters.

As far as the reason, my answer is somewhat "handwavy." Basically, Java's type system was weaker than it is now, and Generics have exposed some low-level implementation details that cause headaches. Specifically, every time you insert an item into an array, you incur a type check at runtime. Java arrays are covariant. Since a type-check occurs at runtime, and the erasure behavior as defined in the spec, the compiler wants to warn you that you should be aware of possible issues down the line.

So more or less, its a design decision that caused lots of headaches after Generics were implemented.

avgvstvs
  • 6,196
  • 6
  • 43
  • 74
3

In addition to Jon's answer, what you perhaps want to do is to declare your variable to still use generics, but accept any type:

Generic<?> generic = ...;

Leaving out the type modifier completely will, as Jon explained, also disable other generics declarations not directly related to the class' type.

jarnbjo
  • 33,923
  • 7
  • 70
  • 94