为什么这个代码在Java 1.6中编译而在Java 1.7中编译?

时间:2013-03-28 12:09:48

标签: java generics java-7 javac java-6

以下代码在Java 1.6中编译良好,但无法在Java 1.7中编译。为什么呢?

代码的相关部分是对私有“数据”字段的引用。引用来自定义字段的同一类,因此看似合法。但它是通过一般类型的变量发生的。这段代码 - 基于内部库中的类的简化示例 - 在Java 1.6中工作,但现在不在Java 1.7中。

我不是在问这个如何解决这个问题。我已经做到了。我试图找到解释为什么这不再起作用的原因。我想到了三种可能性:

  • 根据JLS,此代码为 NOT LEGAL ,并且永远不应编译(1.6编译器中存在错误,1.7中已修复)
  • 根据JLS,此代码是 LEGAL 并且应该编译(1.7编译器中引入了向后兼容性错误)
  • 此代码属于JLS
  • 中的 GRAY AREA

Foo.java:

import java.util.TreeMap;
import java.util.Map;

public abstract class Foo<V extends Foo<V>> {

    private final Map<String,Object> data = new TreeMap<String,Object>();

    protected Foo() { ; }

    // Subclasses should implement this as 'return this;'
    public abstract V getThis();

    // Subclasses should implement this as 'return new SubclassOfFoo();'
    public abstract V getEmpty();

    // ... more methods here ...

    public V copy() {
        V x = getEmpty();
        x.data.clear();      // Won't compile in Java 1.7
        x.data.putAll(data); // "
        return x;
    }

}

编译器输出:

> c:\tools\jdk1.6.0_11\bin\javac -version
javac 1.6.0_11

> c:\tools\jdk1.6.0_11\bin\javac c:\temp\Foo.java

> c:\tools\jdk1.7.0_10\bin\javac -version
javac 1.7.0_10

> c:\tools\jdk1.7.0_10\bin\javac c:\temp\Foo.java
Foo.java:18: error: data has private access in Foo
        x.data.clear();
         ^
Foo.java:19: error: data has private access in Foo
        x.data.putAll(data);
         ^
2 errors

附录。如果引用是私有方法而不是私有成员变量,则会出现同样的问题。这适用于Java 1.6,但不适用于1.7。

Foo2.java:

import java.util.TreeMap;
import java.util.Map;

public abstract class Foo2<V extends Foo2<V>> {

    private final Map<String,Object> data = new TreeMap<String,Object>();

    protected Foo2() { ; }

    // Subclasses should implement this as 'return this;'
    public abstract V getThis();

    // Subclasses should implement this as 'return new SubclassOfFoo();'
    public abstract V getEmpty();

    // ... more methods here ...

    public V copy() {
        V x = getEmpty();
        x.theData().clear();      // Won't compile in Java 1.7
        x.theData().putAll(data); // "
        return x;
    }

    private Map<String,Object> theData() {
        return data;
    }

}

编译器输出:

> c:\tools\jdk1.6.0_11\bin\javac c:\temp\Foo2.java

> c:\tools\jdk1.7.0_10\bin\javac c:\temp\Foo2.java
Foo2.java:18: error: theData() has private access in Foo2
        x.theData().clear();
         ^
Foo2.java:19: error: theData() has private access in Foo2
        x.theData().putAll(data);
         ^

1 个答案:

答案 0 :(得分:18)

所演示的问题似乎与Oracle bug 6904536中报告的行为相符。该错误被关闭为“非问题”,其解释如下:

  

javac的行为符合JLS。另请参阅65585516711619和相关的JLS问题6644562

相应的JLS问题尚未解决,并带有以下注释:

  

类型变量成员资格的简化说明是   欢迎。一个私人成员存在一般困难   变量的界限。正式的这些成员不会成为成员   类型变量本身,虽然javac和Eclipse传统上是它们   成员和代码已经开始依赖于:

class Test {
  private int count = 0;
  <Z extends Test> void m(Z z) {
    count = z.count;  // Legal in javac 1.6, illegal in javac 1.7 due to fix for 6711619
  }
}
     彼得提交了类似的测试:

class A {
  static class B { private String f; }

  abstract static class Builder<T extends B> {
    abstract T getB();

    {
      ((B)getB()).f.hashCode();
      getB().f.hashCode(); // error: f has private access in A.B
    }

  }
}
     

由于交集类型是通过继承和私有构造的   成员永远不会被继承,重新指定交集是很棘手的   拥有私人成员的类型。尽管如此,它将是兼容的   要做的事。

作为参考,JLS的相关部分为§4.4

修改

我实际上倾向于同意这里的JLS,因为当我们从图片中删除泛型时它与自身匹配。考虑这个例子:

static class Parent {

    private int i;

    void m(Child child) {
        i = child.i; //compile error
    }
}

static class Child extends Parent { }

child.i不可见,因为不会继承对私有成员的访问权限。由于Child可以拥有自己的i而没有任何阴影,因此这一点得到了回归:

static class Child extends Parent {
    private int i; //totally fine
}

所以这将是一个罕见的 upcasting 的例子:

void m(Child child) {
    i = ((Parent)child).i;
}

因此,对于图片中的继承可访问性,JLS似乎在这里是正确的,因为V中的Foo<V extends Foo<V>>不一定是Foo<V>,但可能是某种扩展Foo<V>的类型1}}。