我发现了这个JDK错误,想要了解它为什么会发生.
场景(取自错误报告)非常简单:class声明一个private方法,并interface声明一个public具有相同签名的方法.它编译没有错误.
但是,当我运行此代码时,我得到了 IllegalAccessError
interface I {
public void m();
}
class A {
private void m() {
System.out.println("Inside Class A");
}
}
abstract class B extends A implements I {
}
class C extends B {
public void m() {
System.out.println("Inside Class C");
}
}
public class Test {
public static void main(String... args) {
B b = new C();
b.m();
}
}
Run Code Online (Sandbox Code Playgroud)
请帮助我理解为什么这个错误存在,因为我的代码编译正常.
Exception in thread "main" java.lang.IllegalAccessError:
tried to access method A.m()V from class Test
at Test.main(Test.java:25)
Run Code Online (Sandbox Code Playgroud)
它汇编成一切似乎都很好.
但是,它b.m()被翻译为搜索签名m(),B显然A是在接口中的第一个和(预期)之后.在A一个私人m()被发现和爆炸.
语言行为不一致,理论上可以被编译器避免.
改写
在编译期间,找到公共接口方法 - 很好.在运行期间,(无修饰符)签名在A中找到,其中方法是私有的,永远不会到达方法为公共的接口中的签名.
[FYI]使用javap进行反汇编
invokevirtual method .../.../B.m:()V
Run Code Online (Sandbox Code Playgroud)
当然是C对象.
它可以编译,因为类 B 是一个抽象类,它声明它实现接口 I - 它假设实现将具有所需的方法。
对象 b 的类型在编译时声明为 B。如果你像下面的例子那样玩一下,你会发现它是 B 而不是 C:
为了简单起见,如果你在类 c 中有一个新方法
class C extends B {
public void m() {
System.out.println("C.m");
}
public void testFromC() {}
}
Run Code Online (Sandbox Code Playgroud)
然后尝试在你的 main 中调用它,将无法编译。
public static void main(String[] args) {
B b = new C();
b.testFromC(); // doesnt compile
}
Run Code Online (Sandbox Code Playgroud)
而如果你在B中添加一个方法,那就没问题了。
abstract class B extends A implements I {
public void testFromB() { }
}
public static void main(String[] args) {
B b = new C();
b.testFromB(); // compiles
}
Run Code Online (Sandbox Code Playgroud)
当它运行程序时,将其视为类 B 的对象,它会从 A 中找到私有的实现。如果你通过强制转换强制 b 被视为类型 C,它将起作用。
public static void main(String[] args) {
B b = new C();
((C)b).testFromC();
}
Run Code Online (Sandbox Code Playgroud)
另外,如果您删除 A for m 的私有实现,它也可以在没有强制转换的情况下工作。
class A {
//private void m() {
//System.out.println("A.m");
//}
}
Run Code Online (Sandbox Code Playgroud)
这现在有效:
public static void main(String[] args) {
B b = new C();
b.m();
}
Run Code Online (Sandbox Code Playgroud)
因此,据我现在的理解,它看起来像在运行时首先检查 B 或其父级上的方法 m,如果没有找到任何内容,则转到 C 类的 B 的实现。