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)
答案 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
。因此,Alert
是HasId<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;
}
}