2

I currently really can't wrap my head around what is happening here. I have the following (minimal) code:

public class SillyCast {

    interface B {}
    interface D<T extends B> {}
    class C<T extends D<?>> {}
    class A<T extends B> extends C<D<T>> {}
    List<Class<? extends C<?>>> list = new ArrayList<>();

    void m() {
        list.add((Class<? extends C<?>>) A.class);
    }
}

When compiling (and running) this code in Eclipse everything works as expected but when doing it from the command line (Oracle JDK) I get the following error during compiling:

error: incompatible types: Class<A> cannot be converted to Class<? extends C<?>> list.add((Class<? extends C<?>>) A.class);

I know that the Oracle JDK not always behaves exactly the same as Eclipse JDT but this seems weird.

How can I add an instance of A.class into my ArrayList? Intuitively it seems like this should be possible.

Why does the Oracle JDK and Eclipse JDT have different opinions about this cast? Why does the Oracle JDK have problems doing this cast.

At first I thought this might be a bug but I tested this on different Java versions (8 and 11) and the problem happens on all of them. Also this kind of cast seems way too "simple" as this could go unnoticed over multiple versions, so expect this to be done like this by design.

345094
  • 33
  • 3
  • I get a compilation error in intellij `java: incompatible types`. It seems a bit strange what you're doing - creating interface B, creating interface D that is parameterized by a subclass of B (so its parameterized by interface that extends B, but it isn't a subclass of B itself) and then you create 2 static classes (btw: [info](https://stackoverflow.com/a/936951/17105581) ) that are parameterized by subclasses of interfaces? This is super confusing, what are you trying to achieve? There might be a really simple solution that doesnt require such over engineering. Remember KISS :D – ferrouskid Dec 15 '21 at 10:29
  • 1
    Eclipse 4.22 gives an "unchecked cast" warning for the final `list.add` so it is not really happy. – greg-449 Dec 15 '21 at 10:34
  • `static List> list = new ArrayList<>();` this definition of list works fine however. Does this work for you? Any additional information would be helpful, this is a really fun challenge. – ferrouskid Dec 15 '21 at 10:38
  • 1
    @ferrouskid The static was just to make the minimal example runnable. Probably could just have left out static because my actual code doesn't have them anyway. I updated the code so that it do not needs static. > This is super confusing, what are you trying to achieve? I know that the example looks silly in this minimal form but this is a reduction of production code which stretches this over multiple classes and thousands lines of code. I just created this to reduce the problem to a minimal easy to read form. – 345094 Dec 15 '21 at 10:48
  • @greg-449 > Eclipse 4.22 gives an "unchecked cast" warning for the final list.add so it is not really happy. It does. But still this shouldn't prevent the code from compiling. Unchecked casts aren't really the problem of the compiler. Ironically the original code doesn't has this warning although not being completely clear why not (probably because the actual use is too complicated to analyze) – 345094 Dec 15 '21 at 10:50
  • 1
    @ferrouskid The example comes from (inherited) and very complicated production code being used since years and definitely working fine. I just uncovered it because we need to change the JDK for deployment reasons. – 345094 Dec 15 '21 at 10:52

2 Answers2

1

Not sure what you're trying to achieve but this works fine:

public class Main{
    static interface B {}
    static interface D<T extends B> {}
    static class C<T extends D<?>> {}
    static class A<T extends B> extends C<D<T>>{}

    static List<Class<? extends C>> list = new ArrayList<>();
    public static void main(String[] args) {
        list.add(A.class);
        list.add(C.class);
    }
}

This is really confusing, what are you trying to achieve? You are parameterizing by interfaces extending other interfaces (and then by classes extending the classes parameterized by interfaces extending interfaces). This level of complexity indicates that it might need a different, simpler approach :D

EDIT: Does this work for you? (This compiles and runs with no errors):

public static void main(String[] args) {
    List<Class<? super A<?>>> list = new ArrayList<>();
    list.add(A.class);
    list.add(C.class);
}

I might be wrong, this is a bit confusing, but from what I understand C<?> is a superclass of C<D<T>>, so List<? extends C<?>> accepts objects of type C<?> and all its children (variations of C parameterised by any type T), which is why sticking an instance of A in it gives errors.

A being a subclass of C has no effect on List<A> and List<C>, their only relationship is that they are children of List<?>.

This is why I think we have been running into errors, while A is a C<D<T>>, List<A> has no relationship to List<C>. I agree it would feel intuitive if the relationship was preserved.

ferrouskid
  • 466
  • 2
  • 7
  • 1
    I know that the example looks silly in this minimal form but this is a reduction of production code which stretches this over multiple classes and thousands lines of code. I just created this to reduce the problem to a minimal easy to read form. Replacing the code is difficult because there is nobody in the team who understands the business logic of it (I know this isn't great either). – 345094 Dec 15 '21 at 10:54
  • I getchya, decyphering other people's code is always difficult. It's hard to tell what trying to be done here without more context. I still get `incompatible types` compilation error when I try to run your original code however, that means that the production code isn't working as well as we would like or maybe you have reduced the problem slightly incorrectly? – ferrouskid Dec 15 '21 at 11:17
  • Just to clarify. You are getting an incompatible types error when compiling or when running the code? Apart from the question why Eclipse JDT can compile this and Oracle JDK can not, I am trying to understand why these two types wouldn't be compatible. A literally extends C with an class as generic type which should not mater because the > of the cast does not enforces any boundaries. – 345094 Dec 15 '21 at 12:24
  • Although your code solving the example, changing `static List>> list = new ArrayList<>();` to `static List> list = new ArrayList<>();` isn't possible in my use-case because later use of this attribute needs an unbound C and can not work with an raw type C. The thing I don't understand is why the unbound `C>` do not seem to be compatible with `C>`. – 345094 Dec 15 '21 at 13:29
  • Another thing which is kind of strange is that `@SuppressWarnings("unchecked") Class extends C>> cc = (Class extends C>>) new A().getClass(); list.add(cc);` is apparently working. – 345094 Dec 15 '21 at 22:33
  • I think that the relationship doesn't translate in generics - `C>` does capture `C>` but `List>` doesn't capture `List>` [source]:(http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html#FAQ102) – ferrouskid Dec 17 '21 at 21:47
  • Thanks, I think this probably is the underlying reason. – 345094 Dec 20 '21 at 09:04
0

I think as @ferrouskid pointed out the reason why this isn't working is probably that, although being generally assumed, C<?> is no supertype of C<D<?>>.

Although this is more of a confusion tactic than a real solution, my current workaround is to just distract the Oracle compiler by assigning the A.class to a variable before casting it (may require an @SuppressWarnings("unchecked"))

Class<?> aa = A.class;
list.add((Class<? extends C<?>>) aa);

Disclaimer: This kind of operation is very dangerous because the cast may fail at runtime. So only do this if you have complete test coverage of the impacted methods.

Laurel
  • 5,965
  • 14
  • 31
  • 57
345094
  • 33
  • 3