Почему этот код компилируется в Java 1.6, но не в Java 1.7?
Следующий код компилируется в Java 1.6, но не компилируется в Java 1.7. Почему?
Соответствующая часть кода - это ссылка на личное поле данных. Ссылка находится внутри того же класса, в котором поле определено, и поэтому кажется законным. Но это происходит с помощью типично типизированной переменной. Этот код - урезанный пример, основанный на классе из внутренней библиотеки, - работал в Java 1.6, но теперь не в Java 1.7.
Я не спрашиваю, как обойти это. Я уже это сделал. Я пытаюсь найти объяснение, почему это больше не работает. Приходят на ум три возможности:
- Этот код НЕ ПРАВОВЫЙ в соответствии с JLS и никогда не должен компилироваться (была ошибка в компиляторе 1.6, исправлена в версии 1.7).
- Этот код является LEGAL в соответствии с JLS и должен компилироваться (ошибка обратной совместимости была введена в компилятор 1.7)
- Этот код попадает в область GREY в 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;
}
}
Выход компилятора:
> 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
Показанная проблема, похоже, соответствует поведению, описанному в 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
}
}
Питер представил аналогичный тест:
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.
EDIT:
Я обычно соглашаюсь с 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
}
Итак, это был бы редкий пример необходимости увеличения:
void m(Child child) {
i = ((Parent)child).i;
}
Таким образом, с унаследованной доступностью из изображения, JLS кажется здесь правильным, учитывая, что V
в Foo<V extends Foo<V>>
не обязательно Foo<V>
, но может быть некоторым типом, который расширяет Foo<V>
.