"___中的方法___()在无法访问的类或接口"编译错误中定义

And*_*niy 20 java compiler-errors

我发现了一个奇怪的编译限制,我无法解释,我不明白这个限制的原因.

实施例-1:

考虑这些类:

package e1;:

public class C1 {
    enum E1 { A, B, C }
    public E1 x;
}
Run Code Online (Sandbox Code Playgroud)

package e2;:

import e1.C1;
public class C2 {
    public String test(C1 c1) {
        return c1.x.toString();    // here compilation error
    } 
}
Run Code Online (Sandbox Code Playgroud)

这会导致以下编译错误:

错误:(5,20)java:toString()in java.lang.Enum在无法访问的类或接口中定义

实施例-2:

考虑这些类:

package i1;:

public interface I1 {
    int someMethod();
}

public class C1 {
    static class I2 implements I1 {
        public int someMethod() {
            return 1;
        }
    }
    public I2 x = new I2();
}
Run Code Online (Sandbox Code Playgroud)

package i2;:

import i1.C1;
import i1.I1;
public class C2 {
    public static void main(String[] args) {
        C1 c1 = new C1();
        System.out.println(c1.x.someMethod());  // compilation error
    }
}
Run Code Online (Sandbox Code Playgroud)

这也会导致相同的编译错误,但如果我们将违规行更改为:

System.out.println(((I1)c1.x).someMethod());
Run Code Online (Sandbox Code Playgroud)

然后这可以编译并正常工作.


所以,问题是:

为什么需要这种可访问性限制?

是的,我理解C1.E示例-1)和C1.I2示例-2中的类是包私有的.但与此同时很明显,没有人可以为基本接口(I1of Object)的方法分配较弱的访问权限,因此将对象直接转换为其基接口并获得对受限方法的访问将始终是安全的.

有人可以解释这个限制的目的和原因吗?

更新:assylias指出JLS§6.6.1:

只有在类型可访问时,才能访问引用(类,接口或数组)类型的成员(类,接口,字段或方法)或类类型的构造函数.

看起来这是限制,但它没有解释为什么需要这种限制(在上述例子的情况下)......

Mik*_*ail 12

对于实例方法的调用,使用invokevirtual指令.要调用此方法,类必须具有对此方法的已解析引用

来自invokevirtual规范:

链接例外

在解析对方法的符号引用期间,可以抛出与方法解析(第5.4.3.3节)有关的任何异常.

5.4.3.3.方法解决:

要将未解析的符号引用从D解析为类C中的方法,首先解析方法引用给出的对C的符号引用(第5.4.3.1节).

5.4.3.1.类和接口解析:

如果C无法访问(第5.4.4节)到D,则类或接口解析会抛出IllegalAccessError.

5.4.4.访问控制:

当且仅当满足以下任一条件时,类或接口C才能访问类或接口C:

  • C是公开的.

  • C和D是同一运行时包(第5.3节)的成员.

C和D不是来自同一个包装.因此,即使java为您编译此代码,它也会在调用期间抛出IllegalAccessError.编译器足够聪明,可以防止出现这种明显的错误.这些限制来自java的类解析过程的要求.

要调用实例方法,JVM需要两件事:对象的引用和对象的描述(类或接口).通过解决过程访问描述.如果失败,则调用失败.

如果在解析符号引用期间发生错误,则必须在程序中(直接或间接)使用符号引用的某一点抛出IncompatibleClassChangeError(或子类)的实例.

在您的情况下,C2可以访问I1.所以接口调用效果很好.但C2无法访问I2类.这就是为什么如果此代码编译,可能会在运行时抛出IllegalAccessError.

如何重现IllegalAccessError:

  1. 例如,将内部类设置为public并在IDE中编译所有内容
  2. 使内部类包私有,并使用命令行中的javac编译它.
  3. 用生成的cmd替换IDE生成的类
  4. 你会看到类似的东西:

线程"main"中的异常java.lang.IllegalAccessError:尝试从Test.main的Test类访问类qq.Test1 $ I2(Test.java:30)