为什么这些泛型不能在OpenJDK7中编译,而是在OpenJDK6中编译

时间:2013-03-11 03:16:21

标签: java generics openjdk nested-generics

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;
    // }
}

在OpenJDK6上编译很好,但在OpenJDK7中给出:

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

请注意第50行有rawtype警告,因为必须参数化Alert。如果我这样做,例如extends BaseController<Alert<Object>>,代码编译。 但我不能这样做,因为我需要实现getModelClass()。

更新:这是Java 6实现中的一个错误,已在Java 7中修复:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6559182。 (这是我对编译器开发者的问题: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

3 个答案:

答案 0 :(得分:2)

问题是HasId<String>是否是原始类型Alert的超类型。关于这个问题,规范并不十分清楚。

在[4.8]的精神中,原始类型的超类型也应该都是擦除类型。因此Alert应该有一个超类型HasId,而不是HasId<String>。然而,该部分只谈到“超级类/接口”,而不是“超类型”。

根据[4.10]的精神,超类型是通过直接超类型发现的。目前还不清楚该部分如何适用于原始类型。它可能打算裁定原始Alert具有直接超类型HasStringId。这看起来很公平。然后因为HasId<String>HasStringId的直接超类型,通过传递性,HasId<String>Alert的超类型!

混淆的根源在于实际上有两种HasStringId类型,一种是正常的,一种是原始的。尽管HasStringId本身并不是通用的,但它有一个通用的超类型,因此讨论HasStringId的原始版本是有意义的。

规范没有区分正常HasStringId和原始HasStringId。这是一种疏忽。

假设我们将原始HasStringId'表示为Alert,那么[4.10]现在更有意义。原始HasStringId'的直接超级接口是原始HasStringId'。原始HasId的直接超级接口是原始HasId。因此,AlertHasId<String>的超类型,而不是{{1}}。

section 4 of JLS。我在这里链接到上一个JLS,因为JLS 7在4.10.2节中有严重的编辑错误

答案 1 :(得分:1)

对于各种泛型细微差别,Java 7编译器的a number of个记录案例比Java 6编译器更严格。这些案例通常与实际的语言规范有关,变得更加具体。该错误可能与使用任何原始类型基本上“选择退出”继承类型的泛型有关 - 无论它是否正确是有争议的。

编辑:我在list of JDK 7 incompatibilities中找不到此问题。该错误是可重现的using sun-jdk-1.7.0_10,但不适用于Eclipse编译器(当涉及到泛型细微差别时,它在历史上比javac有更好的跟踪记录)。你应该submit an Oracle bug

这是一种可行的解决方法:

class AlertController extends BaseController<Alert<?>> {
    @Override
    @SuppressWarnings("unchecked")
    Class<Alert<?>> getModelClass() {
        return (Class<Alert<?>>)(Class<?>)Alert.class;
    }
}

答案 2 :(得分:1)

我认为这与在没有实际类型参数的情况下如何处理擦除有关。如果在没有任何类型参数的情况下引用参数化类型,则会删除对这些参数的所有引用。

在这种情况下,您使用的参数化类型Alert没有任何类型参数。这会擦除Alert及其超类上的所有类型参数。这会导致HasId的extends-clause中的HasStringId的类型参数被删除。 Alert然后没有HasId<String>的子类,因为HasStringId不再扩展它,而是扩展HasId

Paul B.的解决方法或下面的方法通过始终使用Alert及其类型参数来避免此问题。

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