这是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 个类都来自不同的供应商,具有不同的发布计划。
在 编译时,编译器知道该方法实际上是 from ,并且签名是 。但是,为调用生成的字节码不引用 ;相反,它引用了方法。请注意,返回类型是方法引用的一部分。C
B.foo()
A
foo()->Number
A
B.foo()->Number
当 JVM 执行此代码时,它首先在 中查找方法;当找不到该方法时,将搜索直接超类,依此类推。 找到并执行。foo()->Number
B
A
A.foo()
现在魔术开始了 - B的供应商发布了新版本的B,它“覆盖” A.foo
public class B extends A
{
public static Number foo(){ return 0.2f; }
}
我们从 B 获取了新的二进制文件,并再次运行我们的应用。(请注意,的二进制文件保持不变;它尚未针对新的 .) 进行重新编译。多田!- 现在在运行时!因为JVM的搜索在这段时间里结束了。C
B
C.x
0.2f
foo()->Number
B
这个神奇的功能为静态方法增加了一定程度的活力。但老实说,谁需要这个功能呢?可能没有人。它只会造成混乱,他们希望自己能把它去掉。
请注意,搜索方式仅适用于单个父链 - 这就是为什么当Java8在接口中引入静态方法时,他们必须决定这些静态方法不被子类型继承。
让我们进一步沿着这个兔子洞走下去。假设 B 发布了另一个版本,具有“协变返回类型”
public class B extends A
{
public static Integer foo(){ return 42; }
}
据 B 所知,这编译得很好。Java允许它,因为返回类型是“协变”;这个功能比较新;以前,“重写”静态方法必须具有相同的返回类型。A
这次会是什么?是的!因为 JVM 找不到 ;它被发现在.JVM 考虑并作为 2 种不同的方法,可能支持一些在 JVM 上运行的非 Java 语言。C.x
0.1f
foo()->Number
B
A
()->Number
()->Integer
如果针对这个最新的重新编译,C的二进制文件将引用;然后在运行时,将是 42。C
B
B.foo()->Integer
C.x
现在,B的供应商在听到所有投诉后,决定从B中删除,因为“覆盖”静态方法非常危险。我们从B中获取新的二进制文件,然后再次运行C(无需重新编译C) - 繁荣,运行时错误,因为在B或A中找不到。foo
B.foo()->Integer
这整个混乱表明,允许静态方法具有“协变返回类型”是一个设计疏忽,这实际上只适用于示例方法。
UPDATE - 此功能在某些用例中可能很迷人,例如,静态工厂方法 - 返回,而返回更具体的.API 设计人员必须小心谨慎,并对潜在的危险用法进行推理。如果 和 来自同一作者,并且用户无法对它们进行子类化,则此设计非常安全。A.of(..)
A
B.of(..)
B
A
B