Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 needs a cast while Java 7 didn't - enum.getClass/getDeclaringClass

I realise Java 8 is still in Beta but this one struck me as odd:

public class Fields<C extends Enum<C>> {

    public Fields(Set<C> columns) {
        // A sample column used to find the universe of the enum of Columns.
        C sampleCol = columns.iterator().next();
        // Java 8 needs a cast here.
        Set<C> allColumns = EnumSet.allOf((/*Class<C>)*/ sampleCol.getClass());
        // ... there's more to this that I've deleted.
    }

}

The error reads:

error: incompatible types: inferred type does not conform to equality constraint(s)
            Set<C> allColumns = EnumSet.allOf(sampleCol.getClass());
    inferred: C
    equality constraints(s): C,CAP#1
  where C is a type-variable:
    C extends Enum<C> declared in class Test.Fields
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Enum from capture of ? extends Enum

Is this a bug or a new feature of Java 8?

like image 483
OldCurmudgeon Avatar asked Feb 08 '14 00:02

OldCurmudgeon


1 Answers

Interesting, this is a subtle change in the treatment of raw types.

First, let's clarify your example. The return type of Object.getClass is special:

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.

In this case, X would be the type parameter C, which erases to Enum. So sampleCol.getClass() returns Class<? extends Enum>. EnumSet.allOf declares the type parameter E extends Enum<E>, and in your case ? extends Enum is being inferred as its type argument.

The important part is that Enum is a raw type. The use of raw types has been seen to erase seemingly unrelated generics, for example on this post: Why won't this generic java code compile? In his answer there, Jon Skeet cites JLS §4.8 ("Raw Types") to cover this unintuitive behavior.

Similar behavior seems to be happening in your example with Java 7: EnumSet.allOf(sampleCol.getClass()) is allowed to compile with an "unchecked invocation" warning (this gets hidden by the subsequent "unchecked conversion" warning from assigning the resulting raw EnumSet to Set<C>).

The question becomes: should the occurrence of a raw type in a generic wildcard's bounds allow unchecked conversions? JLS §4.8 makes no mention of this, so it's ambiguous. Possibly it's a bug, but it seems like a reasonable tightening of this behavior. While a standard raw type like Enum might itself be expected from legacy APIs, a "half-baked" type like Class<? extends Enum> could only occur post-generics and so it doesn't really make sense to let it disrupt generic type checking.

Anyway I'm interested to see if anyone can point to documentation about this change - my search didn't turn anything up.


About your specific code: you should use getDeclaringClass() instead. The compiler can't know that calling getClass on a C will return exactly Class<C>; in fact, it won't if used on an enum with a constant-specific class. This is exactly the use case for which Enum declares that method.

like image 52
Paul Bellora Avatar answered Nov 17 '22 04:11

Paul Bellora