This code won\'t compile because of the String
return type of the staticMethod
in Child
.
class Parent {
static void st
This is one of the most bizzare things in Java. Say we have the following 3 classes
public class A
{
public static Number foo(){ return 0.1f; }
}
public class B extends A
{
}
public class C
{
static Object x = B.foo();
}
Let's say all 3 classes are from different vendors with different release schedules.
At compile time of C
, the compiler knows that method B.foo()
is actually from A
, and the signature is foo()->Number
. However, the generated byte code for the invocation does not reference A
; instead, it references method B.foo()->Number
. Notice that the return type is part of the method reference.
When JVM executes this code, it first looks for method foo()->Number
in B
; when the method is not found, the direct super class A
is 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.foo
public class B extends A
{
public static Number foo(){ return 0.2f; }
}
We 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 new B
.) Tada! - C.x
is now 0.2f
at runtime!! Because JVM's searching for foo()->Number
ends in B
this 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"
public class B extends A
{
public static Integer foo(){ return 42; }
}
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.x
be this time? It is 0.1f
! Because JVM does not find foo()->Number
in B
; it's found in A
. JVM considers ()->Number
and ()->Integer
as 2 distinct methods, probably to support some non-Java languages that runs on JVM.
If C
is recompiled against this newest B
, C's binary will reference B.foo()->Integer
; then at runtime, C.x
will be 42.
Now, B's vendor, after hearing all the complaints, decides to remove foo
from 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, because B.foo()->Integer
is 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(..)
returns A
, while B.of(..)
returns a more specific B
. The API designers must be careful and reason about potential dangerous usages. If A
and B
are from the same author, and they cannot be subclassed by users, this design is quite safe.
String
is not a subtype of void
. Now coming to the actual question :
The crux of this limitation is the fact that static
methods do get inherited in Java
but cannot be overriden. If we have to find the motivation for compile-time checking of return types of static
methods, the real question to be asked is Why are static methods inherited in Java but can't be overriden?
The answer is simple.
static
methods can't be overriden
because they belong to a class
and not an instance
. If you want to know the motivation behind this, you can take a look at this question that already has some answers. static
methods are allowed to be inherited because there are countless situations where a subclass would want to reuse a static
method without having to repeat the same code. Consider a crud example of counting the number of instances of a class :
class Parent {
private static int instanceCount = 0;
public Parent() {
++instanceCount;
}
public static int staticMethod() {
return instanceCount;
}
//other non-static/static methods
}
class Child extends Parent {
//.. other static/non-static methods
}
Parent
knows how to count the number of instances
that were created of itself. Child
is a Parent
so Child
should ideally know how to count instances of itself as well. If static
members were not inherited, you would have to duplicate the code in Parent
within Child
as well.
I see nothing wrong with how the compiler treated you, be your methods static or otherwise. Just the next couple of lines in the documentation say:
This rule allows for covariant return types - refining the return type of a method when overriding it. If R1 is not a subtype of R2, a compile-time unchecked warning occurs unless suppressed by the SuppressWarnings annotation (§9.6.3.5).
Clear. R1 must be a subtype of R2 like Integer
is to another Integer
or to Number
. String
is not subtype of void
.
Documentation says: "compile-time unchecked warning occurs...". But what I noticed is that a full-fledged compilation error awaits.
Inheritance still works as expected. Remove your child method and you 'd have access to the parent's, static or otherwise.