Подтвердить что ты не робот

Java 8 Инициализация двойной фигурной скобки и столкновение имен

Следующий класс имеет внутренний класс, называемый Entry. Этот код не будет компилироваться в Java 8, поскольку компилятор предполагает, что Entry, указанный в двойном курсивом, имеет тип Map.Entry, а не Scope.Entry. Этот код компилируется в предыдущих версиях (не менее 6 и 7) JDK, но в JDK нарушен. Мой вопрос: "почему?" Map.Entry не импортируется в этот класс, поэтому нет причин для компилятора предполагать, что значение имеет тип Map.Entry. Есть ли какая-то неявная область применения или что-то для анонимных классов?

Ошибка:

scope/Scope.java:23: error: incompatible types: scope.Scope.Entry cannot be converted to java.util.Map.Entry for (final Entry entry : entries) {
scope/Scope.java:22: error: cannot find symbol put(entry.getName(), entry);

Пример кода:

package scope;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class Scope {

    public static class Entry<T> {
        public String getName() {
            return "Scope";
        }
    }

    public static void main(String[] args) {
        final Set<Entry> entries = new HashSet<>();

        new HashMap<String, Entry>() {{
            // Why does the Java 8 compiler assume this is a Map.Entry
            // as it is not imported? 
            for (final Entry entry : entries) {
                put(entry.getName(), entry);
            }
        }};
    }
}
4b9b3361

Ответ 1

Это определенно не ошибка, это побочный эффект использования инициализации с двойной привязкой.

new HashMap<String, Entry>() {{
    for (final Entry entry : entries) {
        put(entry.getName(), entry);
    }
}};

Этот тип инициализации - это, в основном, умный способ злоупотребления блоки инициализации экземпляра. Он создает анонимный подкласс HashMap с блоком инициализации, а затем копирует этот блок в начало его конструктора по умолчанию, прежде чем вызывать его. Этот подкласс дает приоритет Entry в области своего родителя, а не в области, в которую он вложен. Это объясняется shadowing.

Из 8.1.6. Объявления класса и членов класса

Если сам C является вложенным классом, могут быть определения того же вид (переменная, метод или тип) и имя как m в охватывающих областях. (Области могут быть блоками, классами или пакетами.) Во всех таких случаях, член m, объявленный в или унаследованный тенями C (§6.4.1), другой определения того же вида и имени. [акцент мой]

Здесь C объявлен анонимный внутренний класс. Так как он наследует от HashMap, java.util.Map.Entry тени scope.Scope.Entry.

Что касается того, почему он скомпилировался так, как вы хотели с предыдущими версиями, я понятия не имею. Такое поведение присутствовало в этих версиях (документы, на которые я ссылался, относятся к 7), поэтому он не должен работать. Так что, возможно, эти версии прослушиваются.

Ответ 2

Сферы применения типов и теневое копирование - трудное место для компилятора. Было/есть количество ошибок, связанных с этим - в основном о вложенных/внутренних/анонимных типах. Я не могу найти тот, который касается именно этой проблемы, но я знаю некоторые, которые могут быть связаны с ним. Здесь - это тот, который очень похож на этот случай (вместо переменной типа type вместо переменной типа).

Относительно того, что спецификация говорит о затенении, есть также проблема . Он имеет ссылки на JLS и описывает, что не идеально.