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);
}
}};
}
}
Ответы
Ответ 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 и описывает, что не идеально.