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?
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 whichgetClass
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With