I found this JDK bug and want to understand why it happens.
The scenario (taken from the bug report) is very simple: a class
declaring a private
method, and an interface
declaring a public
method with the same signature. It compiles without error.
However, when I run this code I am getting 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();
}
}
Please help me understand why this error is there as my code is compiling fine.
Exception in thread "main" java.lang.IllegalAccessError:
tried to access method A.m()V from class Test
at Test.main(Test.java:25)
java.lang.IllegalAccessError. Thrown if an application attempts to access or modify a field, or to call a method that it does not have access to. Normally, this error is caught by the compiler; this error can only occur at run time if the definition of a class has incompatibly changed.
lang. NoClassDefFoundError, which means the Class Loader file responsible for dynamically loading classes can not find the . class file. So to remove this error, you should set your classpath to the location where your Class Loader is present.
It compiles as everything seems fine.
However b.m()
is translated as searching the signature m()
, in B
, evidently first in A
and (intended) later in the interfaces. In A
a private m()
is found and bang.
Inconsistent language behaviour, and theoretically avoidable by the compiler.
Reworded
During compilation the public interface method is found - fine. During runtime the (modifierless) signature is found in A where the method is private, never reaching the signature in the interface where the method is public.
[FYI] Disassembly with javap
invokevirtual method .../.../B.m:()V
Of course on a C
object.
This is a known issue and currently tracked here:
JDK-8021581 Private class methods interfere with invocations of interface methods
This ticket does contain a detailed analysis of the issue, discussion of compatibility concerns and risks of proposed solutions and is still open.
Older discussions of the topic can be found here:
JDK-6684387 IllegalAccessError for code passed by compiler
(that one was linked by shmosel in his comment - thanks for that)
JDK-6691741 JLS membership algorithm is too strong for JVMS method resolution
It compiles because class B is an abstract class that declares it implements interface I - it assumes that the implementation will have the required method.
The type of object b is declared as B at compile time. You can see that it is B and not C if you play a bit as in the examples below:
To make it simple with an example, if you have a new method in class c
class C extends B {
public void m() {
System.out.println("C.m");
}
public void testFromC() {}
}
then trying to call this in your main, will not compile.
public static void main(String[] args) {
B b = new C();
b.testFromC(); // doesnt compile
}
While if you add a method in B, then it will be fine.
abstract class B extends A implements I {
public void testFromB() { }
}
public static void main(String[] args) {
B b = new C();
b.testFromB(); // compiles
}
When it runs the program, treating as object of class B, it founds the implementation from A which is private. If you force b to be viewed as of type C by casting, it will work.
public static void main(String[] args) {
B b = new C();
((C)b).testFromC();
}
Also, if you remove the private implementation of A for m it also would work without the casting.
class A {
//private void m() {
//System.out.println("A.m");
//}
}
This works now:
public static void main(String[] args) {
B b = new C();
b.m();
}
So as I understand it right now, it looks like at runtime first it checks for method m on B or its parent, and if it finds nothing, it goes to implementation of B which is of class C.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With