有人可以解释这个Java范围之谜吗?

时间:2011-06-20 20:49:08

标签: java scope

工作中的同伴扩展了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和编译器似乎都是交替行为,也从不同意彼此。

有人可以解释一下吗?

3 个答案:

答案 0 :(得分:4)

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

  

如果类声明了成员类型   用一定的名字,然后   据说这种类型的声明   隐藏任何和所有可访问的   使用。声明成员类型   超类中的同名和   班级的超级接口。

     

一个类继承自它的直接   超类和直接超接口   所有非私人会员类型   超类和超级接口   这两个代码都可以访问   这个班并没有被一个人隐藏   在课堂上宣言。

我们可以推断出:

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

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

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

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

IntelliJ 10.5并没有抱怨第一个例子;显然这个bug已被修复。第二个例子仍然是错误的。

private有一些有趣的东西。为什么规范明确排除private成员,而已经要求成员“可访问”?实际上,B.Inner(6.6.1)可以访问CompileTest,包括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的错误则是将公共名称重新用于私人用途。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

您只需指定要引用的“内部”。 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
        }
    }
}