为什么这些泛型在OpenJDK7中无法编译,但在OpenJDK6中可以?

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

这段代码在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行有一个原始类型警告,因为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)

如果将其参数化为 extends BaseController<Alert<?>>,会发生什么?(此外,错误消息抱怨 Controller extends...,但您发布的代码是 AlertController extends...。您确定您有正确的行吗?) - Ted Hopp
@TedHopp 如果我使用 Alert<?> 进行参数化,我就无法实现 getModelClass()。直到 Paul Bellora 建议将 Class<Alert> 强制转换为 Class<Alert<?>> 的巧妙技巧后,我才能够实现它。 @PaulBellora 不是我想的出这个技巧,感谢你的帮助! - Dzmitry Lazerka
3个回答

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',那么[4.10]现在更有意义了。原始Alert的直接超接口是原始HasStringId'。原始HasStringId'的直接超接口是原始HasId。因此,HasIdAlert的超类型,而不是HasId<String>
请参见JLS第4节。我链接到旧版的JLS,因为JLS 7在4.10.2节中存在严重的编辑错误。

你是对的,至少来自compiler-dev@openjdk.java.net的人也是这么说的: 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 - Dzmitry Lazerka

1

许多关于Java 7编译器比Java 6编译器更加严格的文档记录,其中涉及各种泛型细节。这些情况通常与实际语言规范变得更加具体有关。错误可能与使用任何原始类型基本上“退出”继承类型的泛型有关-是否正确是有争议的。

编辑:我在JDK 7不兼容性列表中找不到此问题。该错误可以通过使用sun-jdk-1.7.0_10进行重现,但无法通过Eclipse编译器进行重现(在处理泛型细节方面,Eclipse编译器历史上的记录要好得多)。您应该提交Oracle错误报告

以下是可能的解决方法:

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

1
我找到了错误报告:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6559182。 感谢您的转换技巧! - Dzmitry Lazerka

1

我认为这与在实际类型参数不存在的情况下如何处理擦除有关。当引用一个未带任何类型参数的参数化类型时,所有对这些参数的引用都被擦除。

在这种情况下,您使用了一个没有任何类型参数的参数化类型Alert。这会擦除Alert及其超类的所有类型参数。这导致HasStringIdextends子句中HasId的类型参数被擦除。因此, Alert不再是HasId<String>的子类,因为HasStringId不再扩展它,而是扩展HasId

保罗·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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接