Why is return-type covariance enforced for hidden static methods?

后端 未结 3 1844
攒了一身酷
攒了一身酷 2021-02-02 17:36

This code won\'t compile because of the String return type of the staticMethod in Child.

class Parent {
    static void st         


        
相关标签:
3条回答
  • 2021-02-02 17:38

    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.

    0 讨论(0)
  • 2021-02-02 17:49

    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.

    0 讨论(0)
  • 2021-02-02 18:02

    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.

    0 讨论(0)
提交回复
热议问题