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

Pau*_*aul 16 java generics javac java-6 java-7

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

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

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

编译器输出:

> 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
Run Code Online (Sandbox Code Playgroud)

附录.如果引用是私有方法而不是私有成员变量,则会出现同样的问题.这适用于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;
    }

}
Run Code Online (Sandbox Code Playgroud)

编译器输出:

> 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);
         ^
Run Code Online (Sandbox Code Playgroud)

Pau*_*ora 18

证明的问题似乎与Oracle bug 6904536中报告的行为相符.该错误被关闭为"非问题",并提供以下说明:

javac的行为符合JLS.又见6558551,6711619及相关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
  }
}
Run Code Online (Sandbox Code Playgroud)

彼得提交了类似的测试:

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
    }

  }
}
Run Code Online (Sandbox Code Playgroud)

由于交集类型是通过继承构造的,并且永远不会继承私有成员,因此重新指定交集类型以拥有私有成员是很棘手的.尽管如此,这将是兼容的事情.

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

编辑:

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

static class Parent {

    private int i;

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

static class Child extends Parent { }
Run Code Online (Sandbox Code Playgroud)

child.i不可见,因为不会继承对私有成员的访问权限.这一点是由于它Child可以拥有自己的事实i而没有任何阴影:

static class Child extends Parent {
    private int i; //totally fine
}
Run Code Online (Sandbox Code Playgroud)

所以这将是一个罕见的向上转换必要的例子:

void m(Child child) {
    i = ((Parent)child).i;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 非常好的答案!感谢您做研究和解释. (4认同)