Java generic type issue

2020-07-19 18:57发布

问题:

Consider the following simplified example:

package com.test;

class B<S> {
    B(Class<S> clazz) {}
}

class A<T> {
    class SubB extends B<SubB> {
        SubB() {
            super(SubB.class);
        }
    }
}

Although IntelliJ is not showing any error (as it usually does when compile errors exist), the actual compilation when starting the program ends with error located in super(SubB.class);:

Error:(8, 23) java: incompatible types: java.lang.Class<com.test.A.SubB> cannot be converted to java.lang.Class<com.test.A<T>.SubB>

I am curious, why is this happening? And how could I solve it?

Compilation is done with AdoptOpenJDK 11.

回答1:

The reason for this behavior is a bit complicated. Consider java.util.List.class, which has the type Class<java.util.List>, not Class<java.util.List<?>>. This is a limitation of the class literal.

In your example, SubB.class has the type Class<com.test.A.SubB>, again with the raw type of SubB. But the constructor expects some type of Class<com.test.A<T>.SubB>.

That's why we need to cast the literal to it's desired type:

super((Class<SubB>) (Class<?>) SubB.class);

This will produce a warning, but a quick examination will show that there is nothing to worry about.



回答2:

I found this very interesting.

The problem here is that when you declare this:

class A<T> {

    class SubB extends B<SubB> {
        SubB() {
            super...
        }
    }
}

that B<SubB> is actually B<A<T>.SubB> (I was not even aware this is possible). This is easy to prove thx for javap (just decompile the class and see for yourself). Once you write that in the "long" form:

class A<T> {
    class SubB extends B<A<T>.SubB> {
        SubB() {
            super(....);
        }
    }
}

it starts to make a lot more sense.

Ideally for this to work, you have to be able to write:

super(A<T>.SubB.class);

but java does not allow this, .class can only be invoked on raw types.

The best way to do it is to be explicit here:

class A<T> {
    class SubB extends B<A.SubB> {
        SubB() {
            super(SubB.class);
        }
    }
}

in saying: I am using the raw type of A: B<A.SubB>



回答3:

In generics the inheritance is not as we usually know, i.e lets take an example class ArrayList<String> is not a subclass of List. Whereas List<String> is not same as List.

Also in generics you will not generally get a compilation error easily since the Generic types are been transformed to their raw types during the compilation.

Hence we need to cast as mentioned by @JohannesKuhn.



标签: java generics