I encountered some weird behavior in a project I was working on and made this example:
class Main implements ClassGetter{
public static void main(String[] args) {
Main m = new Main();
m.test();
}
public void test(){
consumeClazz(this.getClass()); // error in editor
consumeClazz((Class<? extends Clazz>) this.getClass()); // error in editor
consumeClazz(getCastedClass()); // compiles and runs successfully
}
public void consumeClazz(Class<? extends Clazz> clazz){
System.out.println(clazz.getSimpleName());
}
}
interface ClassGetter{
default Class<? extends Clazz> getCastedClass(){
return (Class<? extends Clazz>) this.getClass(); // warning in editor
}
}
class Clazz{}
The focus is the test() method in the Main class and the getCastedClass() method of the ClassGetter interface. The first two lines in the test() method give errors in the editor that explain how Class<capture of ? extends Main> cannot be converted to Class<? extends Clazz>. However, the last line in test() will compile and run successfully, printing out "Main". Interestingly, I get the warning Unchecked cast: 'Class<capture<? extends ClassGetter>>' to 'Class<? extends Clazz>' on the line in the getCastedClass() method, which made me expect the last example wouldn’t work at all like the first two.
Why does the last example work while the first two never compile?
>Solution :
Why is this illegal in Main?
consumeClazz((Class<? extends Clazz>) this.getClass());
An instance of Main cannot possibly be an instance of Clazz because they are unrelated classes, and Java does not support multiple inheritance.
Why is this legal in ClassGetter?
return (Class<? extends Clazz>) this.getClass();
An instance of ClassGetter might be an instance of Clazz, if there is a subclass of Clazz that implements ClassGetter. Because it’s possible, this is legal but not safe.
Why is this legal in Main?
consumeClazz(getCastedClass())
getCastedClass() is declared to return Class<? extends Clazz>, which is compatible with the parameter type for consumeClazz.
Why does it run successfully?
At runtime, the generics are erased, and your Class<...> references are just Class objects. On that basis, getting the class and printing its name is all legitimate.