class HasId<I> {}
class HasStringId extends HasId<String> {}
class Alert<T extends /*Some*/Object> extends HasStringId {}
class BaseController<M extends HasId<String>> {
// abstract Class<M> getModelClass();
}
class AlertController extends BaseController<Alert> { // error here
// @Override Class<Alert> getModelClass() {
// return Alert.class;
// }
}
compiles fine on OpenJDK6, but in OpenJDK7 gives:
AlertController.java:50: error: type argument Alert is not within bounds of
type-variable T
class AlertController extends BaseController<Alert> {
^
where T is a type-variable:
T extends HasId<String> declared in class BaseController
Note that there's rawtype warning at line 50, because Alert must be parameterized. If I do that, e.g. extends BaseController<Alert<Object>>
, code compiles.
But I cannot do that, because I need to implement getModelClass().
UPDATE: That was a bug in Java 6 implementations, which was fixed in Java 7: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6559182. (And here's my question to compiler devs: http://openjdk.5641.n7.nabble.com/Nested-generics-don-t-compile-in-1-7-0-15-but-do-in-1-6-0-27-td121820.html )
The question is whether HasId<String>
is a supertype of the raw type Alert
. The spec is not very clear on this issue.
In the spirit of [4.8], the supertypes of a raw type should all be erased types too. So Alert
should have a supertype HasId
, but not HasId<String>
. However the section talks only in terms of "super classes/interfaces", not in terms of "supertypes".
In the spirit of [4.10], the supertypes are discovered through direct supertypes. It's unclear how the section applies to raw types. It probably intends to rule that raw Alert
has a direct supertype HasStringId
. That seems fair. Then because HasId<String>
is a direct supertype of HasStringId
, by transitivity, HasId<String>
is a supertype of Alert
!
The confusion is rooted in the fact that there are actually two HasStringId
types, one normal, one raw. Even though HasStringId
is not generic in itself, its has a generic supertype, so it makes sense to talk about the raw version of HasStringId
.
The spec does not make a distinction between the normal and raw HasStringId
. That's an oversight.
Suppose we denote the raw HasStringId
as HasStringId'
, then [4.10] makes more sense now. The direct super interface of raw Alert
is raw HasStringId'
. The direct super interface of raw HasStringId'
is raw HasId
. Therefore HasId
is a supertype of Alert
, not HasId<String>
.
See section 4 of JLS. I'm linking to the prev JLS here, since JLS 7 has serious editing errors in section 4.10.2
There are a number of documented cases of Java 7 compilers being stricter than Java 6 compilers for various generics nuances. These cases are often related to the actual language spec having become more specific. The error likely has to do with the use of any raw type essentially "opting out" of generics on inherited types - whether it's correct is debatable though.
EDIT: I couldn't find this issue in the list of JDK 7 incompatibilities. The error is reproducible using sun-jdk-1.7.0_10, but not with the Eclipse compiler (which historically has a much better track record than javac when it comes to generics nuances). You should submit an Oracle bug.
Here's a possible workaround:
class AlertController extends BaseController<Alert<?>> {
@Override
@SuppressWarnings("unchecked")
Class<Alert<?>> getModelClass() {
return (Class<Alert<?>>)(Class<?>)Alert.class;
}
}
I believe this has to do with how erasure is handled in the absence of actual type parameters. When a parameterized type is referenced without any type parameters, all references to those parameters are erased.
In this instance, you have a parameterized type Alert
being used without any type parameter. This erases all type parameters on Alert
and its superclasses. This causes the type parameter of HasId
in the extends-clause of HasStringId
to be erased. Alert
then does not subclass HasId<String>
because HasStringId
no longer extends it but rather extends HasId
.
Paul B.'s workaround or the one below avoids this issue by always using Alert
with its type parameters.
class AlertController<T> extends BaseController<Alert<T>> {
@Override Class<Alert<T>> getModelClass() {
return cast(Alert.class);
}
@SuppressWarnings("unchecked")
private <T> T cast(final Object o) {
return (T) o;
}
}
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