由于 Child
中 staticMethod
的 String
返回类型,此代码无法编译。
class Parent {
static void staticMethod() {
}
}
class Child extends Parent {
static String staticMethod() {
return null;
}
}
我知道 §8.4.8.3 中的 JLS 8,“覆盖和隐藏的要求”说:
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.
我的问题是,在静态方法的特定情况下进行这种编译时检查的动机是什么,最好举例说明在编译期间未能进行这种验证会产生任何问题。
最佳答案
这是 Java 中最奇怪的事情之一。假设我们有以下 3 个类
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()->数字
。但是,为调用生成的字节代码不引用 A
;相反,它引用方法 B.foo()->Number
。请注意,返回类型是方法引用的一部分。
当JVM执行这段代码时,它首先在B
中寻找方法foo()->Number
;当找不到该方法时,将搜索直接父类(super class) A
,依此类推。找到并执行 A.foo()
。
现在魔法开始了 - B 的供应商发布了 B 的新版本,“覆盖” A.foo
public class B extends A
{
public static Number foo(){ return 0.2f; }
}
我们从 B 那里得到了新的二进制文件,然后再次运行我们的应用程序。 (注意 C
的二进制文件保持不变;它没有针对新的 B
重新编译。) Tada! - C.x
在运行时现在是 0.2f
!!因为 JVM 这次搜索 foo()->Number
以 B
结束。
这个神奇的特性为静态方法增加了一定程度的活力。但老实说,谁需要这个功能?可能没有人。它只会造成困惑,他们希望可以消除它。
请注意,搜索方式仅适用于父链的单链 - 这就是为什么当 Java8 在接口(interface)中引入静态方法时,他们必须决定这些静态方法不被子类型继承。
让我们更深入地了解这个兔子洞。假设 B 发布了另一个版本,具有“协变返回类型”
public class B extends A
{
public static Integer foo(){ return 42; }
}
据 B 所知,这可以很好地针对 A
进行编译。 Java 允许它,因为返回类型是“协变的”;这个功能比较新;
以前,“覆盖”静态方法必须具有相同的返回类型。
这次 C.x
会是什么?它是 0.1f
!因为JVM在B
中没有找到foo()->Number
;它位于 A
中。 JVM 将 ()->Number
和 ()->Integer
视为两种不同的方法,可能是为了支持在 JVM 上运行的一些非 Java 语言。
如果针对这个最新的B
重新编译C
,C 的二进制文件将引用B.foo()->Integer
;然后在运行时,C.x
将为 42。
现在,B 的供应商在听到所有提示后,决定从 B 中删除 foo
,因为“覆盖”静态方法非常危险。我们从 B 获取新的二进制文件,然后再次运行 C(无需重新编译 C)——砰,运行时错误,因为在 B 或 A 中找不到 B.foo()->Integer
。
这整个困惑表明允许静态方法具有“协变返回类型”是一种设计疏忽,这实际上只适用于实例方法。
更新 - 此功能在某些用例中可能很有吸引力,例如,静态工厂方法 - A.of(..)
返回 A
,而 B.of(..)
返回一个更具体的 B
。 API 设计者必须小心并推理潜在的危险用法。如果A
和B
来自同一作者,并且用户不能子类化,这种设计是很安全的。
关于java - 为什么对隐藏的静态方法强制执行返回类型协变?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30875047/