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

时间:2014-11-13 16:12:10

标签: java java-8 inner-classes shadowing

以下类有一个名为Entry的内部类。此代码不会在Java 8中编译,因为编译器假定双花括号初始值设定项中引用的Entry类型为Map.Entry而不是Scope.Entry。这段代码在JDK的先前版本(至少6和7)中编译,但在JDK 8中被破坏。我的问题是“为什么?” 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);
            }
        }};
    }
}

2 个答案:

答案 0 :(得分:26)

这绝对不是一个错误,它是使用双括号初始化的副作用。

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

这种初始化基本上是滥用instance initialization blocks的一种聪明方式。它使用初始化块创建HashMap的匿名子类,然后在调用之前将该块复制到其默认构造函数的开头。此子类优先考虑其父级范围内的Entry,而不是它嵌套的范围。这由shadowing解释。

来自8.1.6. Class Body and Member Declarations

  

如果C本身是嵌套类,则可能存在相同的定义   kind(变量,方法或类型)和名称作为封闭范围中的m。   (范围可以是块,类或包。)在所有这些情况下,   成员m在中声明或由C 阴影(第6.4.1节)继承   相同种类和名称的定义。 [强调我的]

这里,C是声明的匿名内部类。由于它继承自HashMapjava.util.Map.Entry阴影scope.Scope.Entry

至于为什么它按照你想要的版本编译,我不知道。这些行为出现在那些版本中(我引用的文档来自7),所以它不应该有效。所以也许那些版本会被窃听。

答案 1 :(得分:0)

类型成员和阴影的范围对于编译器来说是一个困难的地方。有与此相关的错误数量 - 主要是嵌套/内部/匿名类型。我无法找到完全关于该问题的那个,但我知道一些可能与它有关。 Here是一个与此类似的情况(类型变量而不是封闭类型)。

关于阴影的说明,还有一个issue。它引用了JLS并描述了什么不理想。