Jens
Jens

Reputation: 9406

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?

Upvotes: 2

Views: 668

Answers (3)

Jorn Vernee
Jorn Vernee

Reputation: 33865

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.

Upvotes: 2

Mark Jeronimus
Mark Jeronimus

Reputation: 9543

When I extract a variable for the obtained Class and let my IDE 'fix' everything it can, I get this:

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

As you see, aClass is unparametrized (there is a warning on Enum saying that it is 'raw'). Moreover it states it can be any subclass of Enum, whereas barEnum accepts only a specific enum, and since specific enums are final classes (they can't have subclasses) there is a conflict.

When I then change batEnum to

private static <T extends Enum<T>> EnumSet<T> barEnum(Class<? extends T> x) {

the error goes away since now it accepts subclasses of Enum.

Upvotes: 0

SeverityOne
SeverityOne

Reputation: 2691

My recommendation would be to read the excellent Java Generics FAQ: http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

Off the top of my head, it won't compile because static methods don't inherit the type parameters of a generic class. Then, you're trying to get runtime class information of a parametrised type, which won't work because Java uses erasure for its generics implementation.

Generics in Java are not at all easy, once you move beyond simple collections and such. This is why the FAQ is an indispensable resource.

Upvotes: 0

Related Questions