【问题标题】:Can someone explain this Java scoping mystery?有人可以解释这个 Java 作用域之谜吗?
【发布时间】:2011-06-20 20:49:07
【问题描述】:

工作中的一个同伴扩展了 LinkedHashMap,并覆盖了removeEldestEntry,类似于:

import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class CompileTest {
    static class MyMap<K, V> extends LinkedHashMap<K, V> {
        protected boolean removeEldestEntry(Entry<K, V> eldest) {
            return true;
        }
    }
}

注意参数类是Entry,而不是Map.Entry
Eclipse 生成了方法签名。 IntelliJ 显示错误,抱怨 Entryjava.util.LinkedHashMap 中具有私有访问权限,并且更喜欢 Map.Entry。但它仍然可以编译。

我写了一个小例子来做实验:

public class CompileTest {

    static class A{
        public class Inner {
        }

        public void doStuff(Inner a){}
    }

    static class B extends A{
        private class Inner {
        }
    }

    static class C extends B {
        public void doStuff(Inner a) { }
    }
}

现在 IntelliJ 不显示错误,但类无法编译。以下是两种看起来相同的情况,IDE 和编译器似乎在交替行为,而且彼此之间也从来没有达成一致。

谁能解释一下?

【问题讨论】:

  • 编译器始终是 正确 参考...而且您的示例与 EntryMap.Entry 的做法不同,后者是 @ 的内部类987654331@制作EntryMap的内部类
  • 这两个例子的静态导入有区别吗?
  • 他们看起来一样吗?您的测试用例中的 import 语句在哪里?导入有效地建立了别名,以消除给定非限定名称引用的完全限定名称的歧义。你的测试用例没有。
  • +1 给贾罗德。您的示例并未反映您最初在问题中陈述的范围界定问题。一方面,您的内部类定义太深了,并且您还在 B 类中将 Inner 类声明为私有类,从而有效地将其隐藏在 C 类中。这就是您的编译错误的根源。
  • 顺便说一下,这个错误在 Eclipse 中。它生成的代码完全是错误的。使用 Eclipse 生成的代码,HashMap.Entry 优先于 Map.Entry。

标签: java scope


【解决方案1】:

来自 Java 语言规范 - 8.5 Member Type Declarations

如果类声明了成员类型 有一个特定的名字,然后 据说该类型的声明 隐藏所有可访问的 成员类型的声明与 超类中的同名和 类的超接口。

一个类从它的直接继承 超类和直接超接口 所有非私人成员类型 超类和超接口 都可以通过代码访问 类而不是被隐藏 类中的声明。

我们可以推断:

B.Inner 隐藏A.InnerB 不继承 A.InnerA.Inner 不是 B 的成员 (8.2) 类型。 C 不能从 B 继承 A.Inner。 C 不能从 B 继承 B.Inner,因为它是私有的。

因此C 没有成员类型Inner。假设 C 的封闭作用域(外部类;编译单元;包)中没有其他 Inner 类型,则类型名称 Inner 无法解析。

javac 尝试报告更详细的错误,但这只是对您意图的猜测。更好的错误消息可能应该包含上述所有解释。

在关于Entry的第一个示例中,import语句在整个编译单元范围(即java文件)中声明了Entry,因此Entry被解析为Map.Entry

IntelliJ 10.5 不会抱怨第一个示例;显然错误已被修复。第二个例子仍然是错误的。

private 有一些有趣的地方。为什么规范明确排除 private 成员,而它已经要求成员是“可访问的”?实际上B.Inner 可以在CompileTest (6.6.1) 的整个主体中访问,包括CC 可以有一个 doStuff(B.Inner) 并且它会编译。

这可能就是 IntelliJ 在第二个示例中搞砸的原因;它认为B.Inner 可以被C 访问,因此C 继承B.Inner。它忽略了继承中private 成员的额外排除子句。

这揭示了关于private 声明范围的两种相互冲突的观点。视图#1 希望范围是直接封闭类,视图#2 希望它是顶级封闭类。 #2 程序员鲜为人知,他们会惊讶地发现C 可以访问B.Inner。 #2 可以通过以下方式证明:无论如何它们都在同一个文件中,因此不需要封装,我们不会通过禁止访问来保护任何人;在大多数用例中,允许访问更方便。

观点#2 可能还认为C 应该继承B.Inner - 会出什么问题?有趣的是,#2 在一般访问控制规则上胜出,但在继承规则上输了。

规范真的很乱,幸运的是我们通常不会遇到这些复杂的情况。 LinkedHashMapHashMap 将公共名称重新用于私人用途,这完全是错的。

【讨论】:

    【解决方案2】:

    那是因为EntryMap.Entry 是两个不同的类。第一个具有默认范围并在HashMap 中定义,后者是public interface。因此,您在具有更广泛范围的方法中使用私有参数。这确实可以编译,但可能不是您的意图。

    【讨论】:

    • 是的,A.Inner 和 B.Inner 是 2 个不同的类。然而一个编译,另一个不编译。这两种情况有什么区别?此外,IntelliJ 在这两个方面都不同意编译器。我想我刚刚发现了 2 个 IntelliJ 错误?或者更微妙的东西?这就是奥秘。
    • 就像我说的,它是作用域。由于 Entry 具有默认访问权限,因此您实际上只能从此类或其任何继承者中调用此方法。但是同一包中的类,您通过定义您的方法“受保护”来授予访问权限,实际上无法调用此方法,因为它们无法访问参数。所以编译这个是合法的,但是 IntelliJ 抱怨,因为这没用。
    • 使用@override 注解表明它确实覆盖了所需的方法。不是没用,IntelliJ 也没必要抱怨(据我所知)。
    【解决方案3】:

    您只需指定要引用的“内部”。 IDE 只是帮助您的工具;编译器始终是权威参考。

    ...
    static class C extends B {
    
        public void doStuff(A.Inner a) {
        }
    }
    ...
    

    为了进一步澄清,您可以使用标准变量获得相同的“微妙/奇怪”行为。私有定义正在遮蔽公共定义。

    public class CompileTest {
    
        static class A {
    
            public static Object foo;
    
        }
    
        static class B extends A {
    
            private static Object foo;
    
        }
    
        static class C extends B {
    
            public void doStuff() {
                foo = new Object(); // Will not compile - 'foo' is not visible
                A.foo = new Object(); // Works
            }
        }
    }
    

    【讨论】:

    • 我知道如何让它发挥作用。我问的是一种微妙而奇怪的行为。我的问题是“有人可以解释一下吗?”。不是“我该如何改变它才能让它工作?”
    • 我没有看到任何微妙或奇怪的地方。
    • 一个世界级的IDE静态代码分析错了两次很奇怪。
    • 世界级...呵呵 :P 这些有趣的陈述是这些个人意见。
    • @Tim Bender 根据 Tiobe 和类似的说法,Java 是最常用的 1 或 2 种语言之一。 IntelliJ 是可用的前 2 或 3 个 Java SE/EE(和 Scala/Clojure)IDE(以及 Eclipse 和 Netbeans)。我不认为有什么非常私人或有趣的。有没有“Java IDE 的实用哲学”stackexchange 我们可以继续这个对话?
    猜你喜欢
    • 2020-09-12
    • 1970-01-01
    • 2013-01-09
    • 2012-04-19
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多