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

3

8
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: https://bugs.java.com/bugdatabase/view_bug?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 )

Crispation answered 11/3, 2013 at 3:16 Comment(2)
What happens if you parameterize it as extends BaseController<Alert<?>>? (Also, the error message complains about Controller extends..., but your posted code is AlertController extends.... Are you sure that you have the correct line?)Granddaughter
@TedHopp If I parameterize with Alert<?>, I cannot implement getModelClass(). Couldn't, until Paul Bellora suggested a nice trick to cast Class<Alert> to Class<Alert<?>>. @PaulBellora it's not me. Thanks for the cast trick!Crispation
A
2

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

Abdul answered 11/3, 2013 at 21:19 Comment(1)
You're right, at least guys from [email protected] say the same: openjdk.5641.n7.nabble.com/…Crispation
I
1

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;
    }
}
Indefinite answered 11/3, 2013 at 15:3 Comment(1)
I've found the bug report: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6559182. Thanks for the cast trick!Crispation
K
1

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;
    }
}
Keratosis answered 11/3, 2013 at 21:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.