I've faced the following weird case of an incompleteness of the Java/JVM specification. Suppose we have the classes (we will use Java 1.8 and HotSpot):
public class Class {
public static void foo() {
System.out.println("hi");
}
}
public class User {
public static void main(String[] args) {
Class.foo();
}
}
Then recompile the Class to be an interface without recompiling the
User`:
public interface Class {
public static void foo() {
System.out.println("hi");
}
}
Running the User.main
now produces the same output hi
. It seems obvious, but I would expect it to fail with the IncompatibleClassChangeError
and that's why:
I know that changing a class to an interface is a binary incompatibility according to JVM 5.3.5#3 statement:
If the class or interface named as the direct superclass of C is, in fact, an interface, loading throws an
IncompatibleClassChangeError
.
But let's assume we don't have inheritors of the Class
. We now have to refer the JVM specification about method's resolution.
The first version is compiled into this bytecode:
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method examples/Class.foo:()V
3: return
So we have here something called CONSTANT_Methodref_info
in classpool.
Let's quote the actions of the invokestatic.
... The run-time constant pool item at that index must be a symbolic reference to a method or an interface method (§5.1), which gives the name and descriptor (§4.3.3) of the method as well as a symbolic reference to the class or interface in which the method is to be found. The named method is resolved (§5.4.3.3).
So the JVM treats method and interface methods in a different manner. In our case, JVM sees the method to be a method of the class (not interface). JVM tries to resolve it accordingly 5.4.3.3 Method Resolution:
According to JVM specification, the JVM must fail on the following statement:
1) If C is an interface, method resolution throws an IncompatibleClassChangeError.
...because Class
is not actually a class, but an interface.
Unfortunately, I haven't found any mentions about binary compatibility of changing a class to interface in the Java Language Specification Chapter 13. Binary Compatibility. Moreover, there is nothing said about such tricky case of referencing the same static method.
Could anybody elaborate on that and show me if I missed something?
First, since your example doesn’t contain inheritance, we don’t have to “assume we don't have inheritors of the Class”, there simply are none. So the cited part of §5.3.5 is irrelevant for this example.
The cited part of §6.5, naming “symbolic reference to a method or an interface method” is ironically a change made an Java 8 to relax the restrictions. The invokestatic
instruction is explicitly allowed to be invoked on interface methods, if they are static
.
The first bullet of §5.4.3.3, which you refer to at the end, stating that the method resolution should fail unconditionally, if the declaring type is an interface
, is indeed violated, but it makes no sense anyway. Since it’s referred to unconditionally, i.e. the documentation of invokestatic
doesn’t state that a different lookup algorithm should be used for interface methods, it would imply that invoking a static
method of an interface
is impossible in general.
That’s obviously not the intention of the specification, which incorporates the explicitly added feature of static
methods in interface
s, which of course shall also be callable.
In your example, the calling class does indeed violate the specification, namely §4.4.2, as it refers to an interface method via CONSTANT_Methodref_info
instead of CONSTANT_InterfaceMethodref_info
, after the declaring class has been changed. But the current state of the invokestatic
instruction’s documentation doesn’t mandate to change the behavior based on the constant pool item’s type (that might be the actual intention, but it’s not there). And, as said, adhering to current wording would imply rejecting any invokestatic
on an interface
.
So the specification needs another cleanup, but since the distinction between Methodref_info
and InterfaceMethodref_info
is by far not as useful as it might have been before Java 8 (compare with the answer linked above), I wouldn’t be surprised, if the ultimate fix turns out to be removing the distinction altogether in the future.
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