4

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.

9
  • 1
    Eclipse reports a compiler error The constructor B<A<T>.SubB>(Class<A.SubB>) is undefined Commented Feb 16, 2020 at 11:48
  • 1
    Does it work if you remove <T>? Commented Feb 16, 2020 at 11:48
  • 1
    @assylias It does. Also works when SubB is made a top-level class. I suppose it would also work when SubB is made a static nested class. Commented Feb 16, 2020 at 11:49
  • 3
    Is it really necessary for SubB to be an inner class of A? Can you make it a nested class instead? (static class SubB extends B<SubB>) Commented Feb 16, 2020 at 11:51
  • 1
    It seems to be because SubB is an inner class, so T is also part of SubB's type, but SubB.class cannot refer to T as there is only one SubB.class for every T. Try making class SubB a static class SubB. Or try casting SubB.class to Class<A<T>.SubB. Commented Feb 16, 2020 at 11:53

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

7 Comments

1) according to this, the fact that getClass will return a raw type is mandated by the specification, not sure if this is what you meant by limitation. 2) ....again with the raw type of SubB, you mean .. raw type of A? 3) while the solution does work, IMHO it does explain why that happens. Why would the constructor expect Class<com.test.A<T>.SubB>, why is that A<T> even part of the compiler messages considering that point (1) mandates the use of reified type, which would be A.class, not an impossible A<T>.class
and of course that once you cast to Class<?> - you can further cast to everything you want: Class<Integer> c = (Class<Integer>) (Class<?>) SubB.class;
@Eugene mind that SubB is not static, therefore, it has a type parameter (T), corresponding to its outer instance of A<T>. SubB can refer to T in its body, e.g. having methods accepting or returning T. Hence, SubB is a generic class and not reifiable, so SubB.class refers to the raw type SubB without an actual type argument for T. I do not agree with the answer’s conclusion that “a quick examination will show that there is nothing to worry about”. There is no problem with the question’s simplified example, but that’s surely not the real code.
A better solution is to use class SubB extends B<A.SubB> to emphasize that the type argument to S has a raw A. This eliminates the unchecked operation in the constructor and may raise warnings where it matters, at potential uses of S depending on A (if such places exist). (Or change SubB to static, if a dependency to an A<T> was not intended at all).
@Eugene Given a particular instance of Inner, you can't determine the actual parameter type of useE due to type erasure (and getClass() reflects the raw type of Inner). These are the typical attributes of generic classes. I don't know whether this name is specifically used for such a local type, but at least, it's a non-reifiable type, which is already enough to have all those problems.
|
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>

1 Comment

That's interesting, indeed. Fun fact: IntelliJ shows a error on that super(SubB.class) but starting the program works.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.