Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type mismatch for Class Generics

Tags:

I have the following code that won't compile and although there is a way to make it compile I want to understand why it isn't compiling. Can someone enlighten me as to specifically why I get the error message I will post at the end please?

public class Test {     public static void main(String args[]) {         Test t = new Test();         t.testT(null);     }      public <T extends Test> void testT(Class<T> type) {         Class<T> testType = type == null ? Test.class : type; //Error here         System.out.println(testType);     } } 

Type mismatch: cannot convert from Class<capture#1-of ? extends Test> to Class<T>

By casting Test.class to Class<T> this compiles with an Unchecked cast warning and runs perfectly.

like image 229
Henry B Avatar asked Oct 02 '08 16:10

Henry B


2 Answers

The reason is that Test.class is of the type Class<Test>. You cannot assign a reference of type Class<Test> to a variable of type Class<T> as they are not the same thing. This, however, works:

Class<? extends Test> testType = type == null ? Test.class : type; 

The wildcard allows both Class<T> and Class<Test> references to be assigned to testType.

There is a ton of information about Java generics behavior at Angelika Langer Java Generics FAQ. I'll provide an example based on some of the information there that uses the Number class heirarchy Java's core API.

Consider the following method:

public <T extends Number> void testNumber(final Class<T> type) 

This is to allow for the following statements to be successfully compile:

testNumber(Integer.class); testNumber(Number.class); 

But the following won't compile:

testNumber(String.class); 

Now consider these statements:

Class<Number> numberClass = Number.class; Class<Integer> integerClass = numberClass; 

The second line fails to compile and produces this error Type mismatch: cannot convert from Class<Number> to Class<Integer>. But Integer extends Number, so why does it fail? Look at these next two statements to see why:

Number anumber = new Long(0); Integer another = anumber; 

It is pretty easy to see why the 2nd line doesn't compile here. You can't assign an instance of Number to a variable of type Integer because there is no way to guarantee that the Number instance is of a compatible type. In this example the Number is actually a Long, which certainly can't be assigned to an Integer. In fact, the error is also a type mismatch: Type mismatch: cannot convert from Number to Integer.

The rule is that an instance cannot be assigned to a variable that is a subclass of the type of the instance as there is no guarantee that is is compatible.

Generics behave in a similar manner. In the generic method signature, T is just a placeholder to indicate what the method allows to the compiler. When the compiler encounters testNumber(Integer.class) it essentially replaces T with Integer.

Wildcards add additional flexibility, as the following will compile:

Class<? extends Number> wildcard = numberClass; 

Since Class<? extends Number> indicates any type that is a Number or a subclass of Number this is perfectly legal and potentially useful in many circumstances.

like image 66
laz Avatar answered Oct 21 '22 03:10

laz


Suppose I extend Test:

public class SubTest extends Test {   public static void main(String args[]) {     Test t = new Test();     t.testT(new SubTest());   } } 

Now, when I invoked testT, the type parameter <T> is SubTest, which means the variable testType is a Class<SubTest>. Test.class is of type Class<Test>, which is not assignable to a variable of type Class<SubTest>.

Declaring the variable testType as a Class<? extends Test> is the right solution; casting to Class<T> is hiding a real problem.

like image 37
erickson Avatar answered Oct 21 '22 01:10

erickson