为什么私有内部类对静态导入不可见



我定义了一个私有的内部枚举类,并尝试静态导入其中一个枚举值,如下所示:

public class OuterClass {
    private enum InnerEnum {
        ONE,
        TWO
    }
    public static void main(String... args) {
        System.out.print(ONE);
    }
}

这是不可能的,因为下面显示的静态导入语句不可见:

import static OuterClass.InnerEnum.ONE;

我必须将可见性从private扩大到package private才能使其正常工作。我非常了解private的语义,但我的观点是为什么相同的代码,一旦编写为完全限定的枚举值,如下所示:

System.out.print(InnerEnum.ONE);

是有效的,但写成这样:

import static OuterClass.InnerEnum.ONE;
...
System.out.print(ONE);

莫。Java 导入语句(静态或非静态)仅引入别名。但是对于私有类型,我们不允许引入别名。这对我来说很奇怪。

有谁知道这个限制背后的语言设计决策?

在我的情况下,允许静态导入会产生哪些风险或危险?

我对技术动机的原因几乎不感兴趣。

不能

简单地允许 import 语句静态导入内部符号的一个原因是,在同一 Java 源文件中可以有多个顶级符号。

JLS §7.5.3 中给出了单静态导入声明的含义:

单一静态导入声明从类型导入具有给定简单名称的所有可访问静态成员。这使得这些静态成员在出现单静态导入声明的编译单元的类和接口声明中以简单名称可用。

(强调我的)。现在考虑以下代码,它位于单个编译单元中,例如OuterClass.java

package mypackage;
import static mypackage.OuterClass.InnerEnum.ONE;
class OuterClass {
  private enum InnerEnum { ONE }
}
class OtherClass {
  void run() { System.out.println(ONE); }
}

现在:上面的引用说静态导入使ONE符号在整个编译单元中可用 - 其中包括OtherClass - 但InnerEnum不应该在OtherClass中可用,因为它是OuterClass私有的。

基本上,如果由于可见性原因而不应该允许您将该语句编写为System.out.println(OuterClass.InnerEnum.ONE);,则您不应该通过静态导入等另一种机制来规避它。

因此,静态导入私有符号应该(并且是)由语言设计阻止的。(事实上,这不仅限于静态导入:出于完全相同的原因,您也无法import mypackage.OuterClass.InnerEnum

阅读JLS第6.6章后,这是关于访问控制的(https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6)没有关于此问题的直接部分。

但我有一种感觉,作者只是认为你不需要导入私人会员太明显了。

后来,他们也将这种行为转移到静态导入中。

就是这样。

你可以像这样访问它:

public class OuterClass {
    private enum InnerEnum {
        ONE,
        TWO
    }
    public static void main(String[] args) {
        System.out.print(InnerEnum.ONE);
    }    
}

但由于它是私有的,因此您无法从OuterClass

在我看来,

这是对 JLS 如何指定静态导入的错误概念。我将尝试解释我的观点:

  1. 从概念上讲Java 导入或静态导入引入了一个简单的名称作为完全限定名称的别名。导入不会增加 Java 语言的任何功能,但只能提高源代码的可读性。如果你从Java中移除导入,你只会失去可读性。因此,对于Java语言强制执行的所有访问和可见性规则,使用简单名称的源代码和使用完全限定名称的源代码之间应该没有区别。如果您的源代码在没有导入的情况下编译,并且您稍后通过添加导入将完全限定的名称替换为简单名称,则代码应该(在我看来必须)像以前一样编译。

  2. 如果 Java 编译器接受一个根本不定义导入的文件,而是在所有地方使用完全限定的类型,这意味着满足所有访问规则。

  3. 如果Java 编译器将分两个阶段编译文件,如下所述,则可以静态导入具有专用访问范围的实体:

    1. 将所有简单名称替换为完全限定名称
    2. 编译文件。

Java 编译器实现中的缺陷是它确实应用了忽略使用范围的访问规则。在评估导入语句时强制执行publicpackage private可见性,而不检查引入的简单名称(别名)的使用范围,这过于僵化。

更复杂的Java编译器实现将能够允许私有实体的静态导入,这不会给代码带来任何缺陷或缺点。Java 编译器的工作方式现在要么强制执行可读性较低的代码(在某些情况下),要么将私有实体的可见性扩大到私有包。

此外,JLS(在我看来)应该尽可能摆脱技术问题,引入了一个关于语言设计无动机的规则。如上文 1 中所述,没有必要在不尊重使用范围的情况下强制执行或假设可见性。

我想在引入static imports之前,没有导入私有类型的有效方案。因此,在解析所有导入时,可以假定该类型是公共的,或者与定义导入的类型位于同一包中。我不知道为什么编译器确实会检查导入的可见性,因为在解析使用该类型的令牌时检查它应该就足够了,并且由于存在具有完全限定类型的源代码,无论如何都应该发生。

引入static imports后,假设变得不真实,但编译器仍然存在。我们没有纠正编译器来解决新引入的私有实体有效的场景,而是坚持旧行为。仅使用完全限定的类型和实体进行编译的代码在静态导入私有实体后可能不会使用它们。在我看来,这种行为是不一致的,可能会使源代码或类设计变得更糟。

最新更新