Java 8 双花括号初始化和名称冲突

2022-09-02 12:57:18

以下类有一个名为 的内部类。此代码不会在 Java 8 中编译,因为编译器假定在双大括号初始值设定项中引用的是类型而不是 。此代码在 JDK 的早期版本(至少 6 和 7)中编译,但在 JDK 8 中已损坏。我的问题是“为什么? 未在此类中导入,因此编译器没有理由假定该值为 类型 。是否有一些隐式范围被引入或匿名类的东西?EntryEntryMap.EntryScope.EntryMap.EntryMap.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 提供优先级,而不是在其嵌套的作用域中。这可以通过阴影来解释。

8.1.6 开始。类主体和成员声明

如果 C 本身是嵌套类,则在封闭作用域中可能存在与 m 相同类型(变量、方法或类型)和名称的定义。(作用域可以是块、类或包。在所有此类情况下,成员 m 在 C 阴影 (§6.4.1) 中声明或由 C 阴影继承相同类型和名称的其他定义。[强调我的]

这里,是声明的匿名内部类。由于它继承自 ,因此阴影 。CHashMapjava.util.Map.Entryscope.Scope.Entry

至于为什么它按照你想要的方式编译以前的版本,我不知道。此行为存在于这些版本中(我引用的文档来自 ),因此它不应该起作用。所以也许这些版本有问题。7


答案 2

类型成员的作用域和阴影对于编译器来说是一个困难的地方。曾经/现在有很多与此相关的错误 - 主要是关于嵌套/内部/匿名类型。我找不到与该问题完全相关的那个,但我知道一些可能与它相关的问题。这是一个与此非常相似的情况(类型变量而不是封闭类型)。

关于规范对阴影的看法,还有一个问题。它引用了JLS,并描述了不理想的内容。


推荐