Java 为什么对隐藏的静态方法强制执行返回类型协方差?

Java 为什么对隐藏的静态方法强制执行返回类型协方差?,java,jls,Java,Jls,由于Child中staticMethod的String返回类型,此代码无法编译 class Parent { static void staticMethod() { } } class Child extends Parent { static String staticMethod() { return null; } } 我知道§8.4.8.3“覆盖和隐藏要求”中的JLS 8规定: 如果返回类型为R1的方法声明d1重写或隐藏 声明

由于
Child
staticMethod
String
返回类型,此代码无法编译

class Parent {
    static void staticMethod() {    
    }
}

class Child extends Parent {
    static String staticMethod() {
        return null;
    }
}
我知道§8.4.8.3“覆盖和隐藏要求”中的JLS 8规定:

如果返回类型为R1的方法声明d1重写或隐藏 声明另一个返回类型为R2的方法d2,则d1必须为 d2的返回类型可替换(§8.4.5),或编译时错误 发生


我的问题是,在静态方法的特定情况下,这种编译时检查的动机是什么,一个例子表明,在编译过程中不进行这种验证会产生任何问题是理想的。

String
不是
void
的子类型。现在进入实际问题:

这种限制的关键是
静态
方法确实在
Java
中被继承,但不能被重写。如果我们必须找到编译时检查
静态
方法返回类型的动机,那么真正要问的问题是,为什么静态方法在Java中被继承,但不能被重写

答案很简单

静态
方法不能被
重写
,因为它们属于
,而不是
实例
。如果你想知道这背后的动机,你可以看看已经有一些答案的问题<允许继承代码>静态方法,因为在无数情况下,子类希望重用
静态
方法而不必重复相同的代码。考虑一个计算类实例数的CRUD示例:

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
知道如何计算自己创建的
实例数
Child
是一个
Parent
,因此
Child
最好也知道如何计算自身的实例。如果未继承
静态
成员,则必须在
中复制
中的代码。

我认为编译器对待您的方式没有任何错误,无论您的方法是静态还是其他。文档中接下来的几行是:

此规则允许协变返回类型—在重写方法时优化方法的返回类型。 如果R1不是R2的子类型,则会出现编译时未检查警告 除非被SuppressWarnings注释(§9.6.3.5)抑制

清楚。R1必须是R2的子类型,就像
Integer
是另一个
Integer
Number
String
不是
void
的子类型

文档说明:“编译时未检查警告发生…”。但我注意到一个完整的编译错误正在等待


继承仍按预期工作。删除子方法,您就可以访问父方法,无论是静态的还是其他的。

这是Java中最奇怪的事情之一。假设我们有以下三节课

public class A
{
    public static Number foo(){ return 0.1f; }
}

public class B extends A
{
}

public class C
{
    static Object x = B.foo();    
}
假设所有3个类都来自具有不同发布计划的不同供应商

在编译
C
时,编译器知道方法
B.foo()
实际上来自
A
,并且签名是
foo()->Number
。但是,为调用生成的字节码不引用
A
;相反,它引用了方法
B.foo()->Number
。请注意,返回类型是方法引用的一部分

当JVM执行此代码时,它首先在
B
中查找方法
foo()->Number
;当找不到该方法时,将搜索直接超类
A
,依此类推<找到并执行code>A.foo()

现在魔术开始了——B的供应商发布了新版本的B,它“覆盖”
a.foo

public class B extends A
{
    public static Number foo(){ return 0.2f; }
}
我们从B获得了新的二进制文件,然后再次运行我们的应用程序。(请注意,
C
的二进制文件保持不变;它没有针对新的
B
进行重新编译!)
C.x
现在在运行时是
0.2f
!!因为JVM这次搜索
foo()->Number
的结果是
B

这个神奇的特性为静态方法增加了一定程度的活力。但老实说,谁需要这个功能呢?可能没人。这只会造成混乱,他们希望能够消除混乱

请注意,搜索方法只适用于单链父类-这就是为什么当Java8在接口中引入静态方法时,他们必须确定这些静态方法不是由子类型继承的

我们再往兔子洞里钻一点。假设B发布了另一个版本,带有“协变返回类型”

据B所知,这可以针对
A
进行编译。Java允许它,因为返回类型是“协变的”;这一特点相对较新; 以前,“重写”静态方法必须具有相同的返回类型

那么这次C.x将是什么?它是
0.1f
!因为JVM在
B
中找不到
foo()->Number
;它可以在
A
中找到。JVM认为
()->Number
()->Integer
是两种不同的方法,可能是为了支持在JVM上运行的一些非Java语言

如果对最新的
B
重新编译
C
,则C的二进制文件将引用
B.foo()->整数
;然后在运行时,
C.x
将为42

现在,B的供应商在听到所有投诉后,决定从B中删除
foo
,因为“覆盖”静态方法非常危险。我们从B中获取新的二进制文件,然后再次运行C(不重新编译C)-boom,运行时错误,因为在B或A中找不到
B.foo()->Integer

整个混乱表明,允许静态方法具有“协变返回”是一种设计疏忽
public class B extends A
{
    public static Integer foo(){ return 42; }
}