Почему этот код компилируется в 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>.