Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

incompatible types required: Class<T> found: Class<CAP#1> where T is a type-variable

Tags:

java

generics

The following code:

public class A<T> {

    Class<T> klass;
    T instance;

    public A(T instance) {
        this.klass = instance.getClass(); // this requires an explicit cast to Class<T> to satisfy the compiler
        this.instance = instance;
    }
}

gives upon compilation:

A.java:7: error: incompatible types
             this.klass = instance.getClass();
                                           ^
required: Class<T>
found:    Class<CAP#1>
where T is a type-variable:
  T extends Object declared in class A
where CAP#1 is a fresh type-variable:
  CAP#1 extends Object from capture of ? extends Object
1 error

Why isn't the compiler satisfied that instance.getClass() will always produce Class<T> (since instance is of type T) and requires an explicit cast instead? Am I safe just adding the explicit cast:

this.klass = (Class<T>) instance.getClass();

... and thus silencing the compiler or is there room for run-time surprises? And if not, why can't the compiler figure that out?

like image 394
Marcus Junius Brutus Avatar asked Sep 18 '14 13:09

Marcus Junius Brutus


1 Answers

Why isn't the compiler satisfied that instance.getClass() will always produce Class (since instance is of type T) and requires an explicit cast instead?

Consider this:

A<Object> a = new A<Object>("Foo");

Calling instance.getClass() won't return a Class<Object> - it will return a Class<String>. They're not the same thing, even though every String is an Object.

You can change the code to:

Class<? extends T> klass;

At that point it's logically safe, but it still requires a cast because Object.getClass() is just declared to return Class<? extends |T|> (as per JLS section 4.3.2):

The type of a method invocation expression of getClass is Class<? extends |T|> where T is the class or interface searched (§15.12.1) for getClass.

The |T| part means the type erasure of T, which in this case is just going to be Object.

Of course, the cast doesn't actually check anything at execution time, and you'll need @SuppressWarnings("unchecked") to silence the compiler completely, but at least the code would make logical sense at that point.

like image 102
Jon Skeet Avatar answered Oct 06 '22 12:10

Jon Skeet