This code won't compile because of the String return type of the staticMethod in Child.
class Parent {
static void staticMethod() {
}
}
class Child extends Parent {
static String staticMethod() {
return null;
}
}
I know that JLS 8 in §8.4.8.3, "Requirements in Overriding and Hiding" says:
If a method declaration d1 with return type R1 overrides or hides the declaration of another method d2 with return type R2, then d1 must be return-type-substitutable (§8.4.5) for d2, or a compile-time error occurs.
My question is what has been the motivation for this compile-time checking in the specific case of static methods, an example ilustrating that the failure to do this verification during compilation would produce any problems would be ideal.
This is one of the most bizzare things in Java. Say we have the following 3 classes
Let's say all 3 classes are from different vendors with different release schedules.
At compile time of
C, the compiler knows that methodB.foo()is actually fromA, and the signature isfoo()->Number. However, the generated byte code for the invocation does not referenceA; instead, it references methodB.foo()->Number. Notice that the return type is part of the method reference.When JVM executes this code, it first looks for method
foo()->NumberinB; when the method is not found, the direct super classAis searched, and so forth.A.foo()is found and executed.Now the magic starts - B's vendor releases a new version of B, which “overrides”
A.fooWe got the new binary from B, and run our app again. (Note that
C's binary stays the same; it has not been recompiled against the newB.) Tada! -C.xis now0.2fat runtime!! Because JVM's searching forfoo()->Numberends inBthis time.This magical feature adds some degree of dynamism for static methods. But who needs this feature, honestly? Probably nobody. It creates nothing but confusions, and they wish they could remove it.
Notice that the way of searching only works for single chain of parents - that's why when Java8 introduced static methods in interfaces, they had to decide that these static methods are not inherited by subtypes.
Let's go down this rabbit hole a little further. Suppose B releases yet another version, with "covariant return type"
This compiles fine against
A, as far as B knows. Java allows it because the return type is "covariant"; this feature is relatively new; previously, "overriding" static method must have the identical return type.And what would
C.xbe this time? It is0.1f! Because JVM does not findfoo()->NumberinB; it's found inA. JVM considers()->Numberand()->Integeras 2 distinct methods, probably to support some non-Java languages that runs on JVM.If
Cis recompiled against this newestB, C's binary will referenceB.foo()->Integer; then at runtime,C.xwill be 42.Now, B's vendor, after hearing all the complaints, decides to remove
foofrom B, because it is so dangerous to "override" static methods. We get the new binary from B, and run C again (without recompiling C) - boom, runtime error, becauseB.foo()->Integeris not found in B or in A.This whole mess indicates that it was a design oversight to have allowed static methods to have "covariant return type", which is really only intended for instance methods.
UPDATE - this feature might be charming in some use cases, for example, static factory methods -
A.of(..)returnsA, whileB.of(..)returns a more specificB. The API designers must be careful and reason about potential dangerous usages. IfAandBare from the same author, and they cannot be subclassed by users, this design is quite safe.