简体   繁体   中英

Why these generics don't compile in OpenJDK7, but do in OpenJDK6

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, eg 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;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM