【问题标题】:Java 8 Double curly bracket initialization and name collisionJava 8 双花括号初始化和名称冲突
【发布时间】:2015-01-10 20:40:47
【问题描述】:

以下类有一个名为Entry 的内部类。此代码不会在 Java 8 中编译,因为编译器假定双花括号初始值设定项中引用的 EntryMap.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);
            }
        }};
    }
}

【问题讨论】:

  • @tobias_k 这不是真正的双花括号。有一对大括号用于创建匿名内部类,另一对用于实例初始化程序块。有些人喜欢将其格式化为双花括号,但实际上并没有什么特殊含义。
  • @tobias_k 是的,实际上初始化程序块被复制到每个构造函数的开头。 (如果有多个块,则按照它们在源代码中出现的顺序执行。)
  • 顺便说一句,“花括号”这个词是我最讨厌的。它们只是“大括号”。
  • 使用entries.stream().collect(toMap(Entry::getName, e-&gt;e))(静态导入Collectors.toMap)而不是“双括号初始化”,这很糟糕。
  • 问题在于它捕获了对封闭类的引用,从而防止在地图存在时对其进行垃圾收集。当然,这在某个地方可能还不错,但是在每次实例化这样的地图之前,您是否会停下来考虑一下?在将一些代码移动到不同的类之前,您项目中的每个开发人员都会考虑这一点吗?在添加大量状态之前,他们是否都会检查类中的双括号习语?我会避免这个成语。还有很多更好的方法。

标签: java java-8 inner-classes shadowing


【解决方案1】:

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

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

这种类型的初始化基本上是滥用instance initialization blocks的巧妙方法。它使用初始化块创建 HashMap 的匿名子类,然后在调用它之前将该块复制到其默认构造函数的开头。这个子类在其父类的范围内赋予条目优先级,而不是在它嵌套的范围内。shadowing 对此进行了解释。

来自8.1.6. Class Body and Member Declarations

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

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

至于为什么它与以前的版本一样编译,我不知道。这种行为出现在那些版本中(我引用的文档来自7),所以它不应该起作用。所以也许这些版本有问题。

【讨论】:

  • 在 JLS 中哪里指定扩展类声明超类的内部类?扩展一个类肯定可以让您访问该类的内部类,但内部类的声明是在其他地方完成的。或者你为什么将子类化和声明解释为相等?
  • 是的,现在你是对的 :) 我试图在 §6.4.1 中找到类似的东西,其中指定了阴影 actually,并且在那段中没有描述这种情况。
【解决方案2】:

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

关于阴影的规范还有一个issue。它引用了 JLS 并描述了不理想的地方。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-20
    • 2017-07-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    相关资源
    最近更新 更多