为什么隐藏的静态方法在Sun JDK 6下编译但在OpenJDK 6和7下导致编译失败?

时间:2011-12-23 21:03:33

标签: java generics java-7

以下课程:

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

1 个答案:

答案 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 都适用,因此含糊不清。