为什么这个代码在Java 1.6中编译而在Java 1.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> { private final Map data = new TreeMap(); 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> { private final Map data = new TreeMap(); 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 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); ^ 

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

javac的行为符合JLS。 另见6558551,6711619和相关的JLS问题6644562 。

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

欢迎对类型变量的成员资格进行简化说明。 类型变量边界的私有成员存在一般困难。 正式地,这样的成员本身并不成为类型变量的成员,尽管javac和Eclipse传统上使它们成为成员并且代码依赖于此:

 class Test { private int count = 0;  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 { abstract T getB(); { ((B)getB()).f.hashCode(); getB().f.hashCode(); // error: f has private access in AB } } } 

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

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

编辑:

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

 static class Parent { private int i; void m(Child child) { i = child.i; //compile error } } static class Child extends Parent { } 

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

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

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

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

因此,从图片中inheritance的可访问性来看,JLS在这里似乎是正确的,因为Foo>不一定是Foo但可能是扩展Foo某种类型。