以下课程:
public class StaticMethodsDemo {
public static class A {
public static A make() { return new A(); };
}
public static class B extends A {
public static B make() { return new B(); };
}
public static class BPrime<T> extends A {
public static <T> BPrime<T> make() { return new BPrime<T>(); };
}
public static void main(String[] args) {
B.make();
// compiles under Sun JDK 1.6.0_20 but fails under Oracle JDK 1.7.0_01. Why?
BPrime.<Object>make();
}
}
在Sun JDK 1.6.0_20(Windows 64位,但不应该有所作为)下编译,但在Oracle JDK 1.7.0_01(相同平台)和OpenJDK 1.6.0_20(Ubuntu)[1]下失败: / p>
[ERROR] StaticMethodsDemo.java:[37,14] error: reference to make is ambiguous, both method make() in A and method <T>make() in BPrime match
为什么呢?通用参数(应该擦除,不是?)如何导致这种明显的不匹配。请注意,删除泛型如下:
...
public static class BPrime<T> extends A {
T val;
public static BPrime<?> make() { return new BPrime<Object>(); };
public void setT(T val) { this.val = val; }
}
public static void main(String[] args) {
B.make();
BPrime<Long> bprime = (BPrime<Long>) BPrime.make();
bprime.setT(Long.valueOf(10));
}
编译并运行(因此泛型黑客不会导致任何奇怪的运行时转换错误)。
Issue 461: jclouds-core compilation fails using stock ubuntu openjdk
答案 0 :(得分:12)
显然,javac6的行为是合理的,javac7不是。
不幸的是,根据规范的字母,javac7是对的。
这是由于java中所有邪恶的根源 - 类型擦除。其动机是在不破坏任何引用旧的非泛化集合API的旧代码的情况下生成集合API。为了简洁起见,我们将其称为最愚蠢的动机。
编译BPrime.<Object>make()
时,第一个javac需要找出包含该方法的类。这很容易上课B'
。 (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.1)
然后我们需要知道类B'
中的所有方法,包括继承的方法。这取决于make()
中的方法B'
( mb )是否隐藏了make()
中的方法A
( ma ) ;这取决于 mb 的签名是否是 ma 的子签名。 (http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.8)
子签名概念的存在也是为了满足最愚蠢的动机。否则,在确定覆盖和隐藏方法时,我们只需要担心相同的签名。
但这次不是问题。根据定义, mb 不是 ma 的子签名,因此 ma 在类B'
中继承。因此,班级B'
有两种make()
方法。
下一步,是确定可能适用的方法。规则说(http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.2.1)
如果方法调用包含显式类型参数,并且成员是泛型方法,则实际类型参数的数量等于形式类型参数的数量。
这意味着 ma 适用于表达式BPrime.<Object>make()
,因为 ma 不是通用方法。什么?!
规范解释
上述条款意味着非泛型方法可能适用于提供显式类型参数的调用。实际上,它可能会变得适用。在这种情况下,类型参数将被忽略。
这条规则源于兼容性和可替代性原则的问题。由于接口或超类可以独立于其子类型进行泛化,因此我们可以使用非泛型方法覆盖泛型方法。但是,重写(非泛型)方法必须适用于对泛型方法的调用,包括显式传递类型参数的调用。否则,子类型不会替代其生成的超类型。
所以这也是为了提供最愚蠢的动机,我们必须允许无意义的语法,如
System.<String,Integer>currentTimeMillis();
然后, mb 和 ma 都适用,因此含糊不清。