A Java class is normally validated at three stages (roughly speaking):
- At its compilation - this (obviously) happens when you compile a Java class.
- When it is loaded into the Java Virtual Machine - this happens when you first reference a class.
- During its actual execution - this happens the latest when you first use a specific code segment.
Most programming errors are caught by the Java compiler. However, the Java compiler is tricked by your method shadowing (hiding) of the pkgs.main.A#mStaPro
method by the pkgs.test.B#mStaPro
method. Here is what happens:
When you compile your Main
class, you are invoking the static
method mStaPro
on B
. The question is, are you invoking the shadowed method that is defined in the pkgs.main.A
class or the hiding method that is defined in the pkgs.test.B
class? In this question, the Java compiler and the Java runtime come to different conclusions which is why the Java compiler approves your code while the Java runtime refuses it:
- The Java compiler considers your code legal because it thinks that you are invoking the
mStaPro
method that is defined inA
. TheA
class lives in the same package asMain
such that itsprotected
methods are considered to be visible to theMain
class. - The Java runtime considers your code illegal because it thinks that you are invoking the
mStaPro
method that is defined inB
. This method is alsoprotected
but it is defined in thepkgs.test
package. Therefore, thismStaPro
must not be invoked fromMain
which lives in a different package.
In order to inform you about this illegal code, the Java runtime throws you the IllegalAccessError
that you encounter. Let us just go one step deeper here. If you look at the generated Java byte code, the Main#m
method is compiled as follows:
invokestatic #5 // Method pkgs/test/B.mStaPro:()V
return
what resembles your Java source code:
B.mStaPro()
return // a void return statement is implicit in Java source code
This compilation result is independent on whether the B
class defines a method mStaPro
or not. And this is the cause of the exception you encounter. In cases that the B
class defines a method mStaPro
, the invokestatic
invocation is bound to the B#mStaPro
method (what is illegal). Otherwise, the invocation is bound to A#mStaPro
(what is legal).
To solve this problem, you should name your actual target class A.mStaPro()
instead of invoking the method on B
. However, I must honestly say that I find the behavior of the Java compiler counter-intuitive. A static
method is, well, static and not dynamic. Therefore, the Java compiler should statically bind the invocation of B.mStaPro()
to pkgs/main/A.mStaPro:()V
at compile time. Effectively, without this assumption, the compilation could not be successful. And even better, it should simply produce a compilation error when it encounters that an inaccessible B
is shadowing the target in A
and that the code cannot run successfully.
Finally, a minor hint on this behavior might be given in Oracle's tutorial on static
invocation which mentions this behavior:
The version of the hidden static method that gets invoked depends on whether it is invoked from the superclass or the subclass.
And yet, this is not good enough for the reasons I mentioned above. However, I worked with Java in many years and since shadowing is not a recommended practice, I never encountered this problem. Putting it like this, you really found a borderline case.
Info: I posted this to the Java compiler mailing list. Once I get an answer, I will add the information to this posting.