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 theUser`:
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?
API class-interface gender changes break binary compatibility, even in cases where the class/interface is used by, but not implemented by, Clients. This is because the Java VM bytecodes for invoking a method declared in an interface are different from the ones used for invoking a method declared in a class. But it's something informal.