javagenericstype-inferenceunchecked-conversion

Generic method performs implicit cast while non-generic method needs explicit cast


This question is related to a previous question. The original can be solved by adding casts to do unchecked conversions. So now I have the following code:

import java.util.EnumSet;

class A {
    static enum E1 {
    X
    }

    private static <T extends Enum<T>> EnumSet<T> barEnum(Class<T> x) {
        return null;
    }

    private static void foo1(EnumSet<E1> s, E1 e) {
        EnumSet<E1> x2 = barEnum((Class<E1>)e.getClass());
    }

    private static void foo2(EnumSet<E1> s) {
        EnumSet<E1> x = barEnum((Class<E1>)s.iterator().next().getClass());
    }
}

My original intent was to write a generic method. So I generalized the method foo2() to:

private static <E extends Enum<E>> void foo3(EnumSet<E> s) {
    EnumSet<E> x = barEnum(s.iterator().next().getClass());
}

This obviously contains unchecked conversions and compiles with the appropriate warning. But I don't cast the result of getClass() explicitly into Class<E>. Since foo1() is one instance of the generic method foo3(), I expected that I need to add the cast here too. Comparing foo1() to foo4()...

    private static void foo4(EnumSet<E1> s) {
        EnumSet<E1> x = barEnum(s.iterator().next().getClass());
    }

...the two are effectively similar (the main difference being the E1 e parameter in foo1()). However foo1() compiles, but foo4() does not compile. I feel that this is inconsistent. Are there any rules allowing implicit conversions for generic methods?


Solution

  • There are 2 things happening. First of all if you look at the javadoc for getClass it will say:

    The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.

    That means that in your generic method, barEnum is invoked with a Class<? extends Enum> instead of a Class<? extends Enum<E>>. Since Enum is a raw type, this creates an unchecked invocation, which in turn means that the return type is erased (see also: Why is generic of a return type erased when there is an unchecked conversion of a method parameter in Java 8?).

    So in that case barEnum is effectively returning a EnumSet, which is a raw type that can be converted unchecked to EnumSet<E>.

    For your non-generic method you'd have to explicitly cast the argument to a Class<E1>, so there is no unchecked conversion of the method arguments and thus no unchecked invocation. But, that also means that the return type is not erased and the compiler can not find a T that is valid for that call.