由于String
中staticMethod
的{{1}}返回类型,此代码无法编译。
Child
我知道§8.4.8.3中的JLS 8,“覆盖和隐藏的要求”说:
如果返回类型为R1的方法声明d1覆盖或隐藏 声明另一个方法d2,返回类型为R2,则d1必须为 d2的return-type-substitutable(第8.4.5节),或编译时错误 发生。
我的问题是在静态方法的特定情况下进行编译时检查的动机是什么,这个例子说明在编译期间未能进行此验证会产生任何问题是理想的。
答案 0 :(得分:11)
这是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()->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获得了新的二进制文件,并再次运行我们的应用程序。 (注意C
的二进制文件保持不变;它没有针对新的B
进行重新编译。)Tada! - C.x
现在在运行时0.2f
!因为JVM搜索foo()->Number
这次会在B
结束。
这个神奇的功能为静态方法增添了一定程度的活力。但老实说,谁需要这个功能呢?可能没有人。它只会产生混淆,他们希望他们可以删除它。
请注意,搜索方式仅适用于父链的单链 - 这就是为什么当Java8在接口中引入静态方法时,他们必须决定这些静态方法不会被子类型继承。
让我们再往下走这个兔子洞吧。假设B发布了另一个版本,带有“协变返回类型”
public class B extends A
{
public static Integer foo(){ return 42; }
}
就B知道而言,这可以很好地对抗A
。 Java允许它,因为返回类型是“协变”;这个功能相对较新;
以前,“重写”静态方法必须具有相同的返回类型。
这次C.x
会是什么?这是0.1f
!因为JVM在foo()->Number
中找不到B
;它出现在A
中。 JVM将()->Number
和()->Integer
视为两种不同的方法,可能是为了支持在JVM上运行的一些非Java语言。
如果C
针对此最新B
重新编译,则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
来自同一作者,并且用户无法对其进行子类化,则此设计非常安全。
答案 1 :(得分:1)
String
不是void
的子类型。现在回答实际问题:
此限制的关键在于static
方法确实在Java
中继承,但无法覆盖。如果我们必须找到static
方法的返回类型的编译时检查的动机,那么要问的真正问题是为什么静态方法在Java中继承但不能被覆盖?< / em>
答案很简单。
static
方法不能overriden
,因为它们属于class
而非instance
。如果你想知道这背后的动机,你可以看看已经有一些答案的this问题。允许继承static
个方法,因为有无数种情况,子类希望重用static
方法而不必重复相同的代码。考虑一个计算类实例数的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
知道如何计算自己创建的instances
的数量。 Child
是Parent
,因此理想情况下Child
应该知道如何计算自身的实例。如果static
成员未被继承,则您必须在Parent
内复制Child
中的代码。
答案 2 :(得分:0)
我认为编译器如何对待你,你的方法是静态的还是其他方面都没有错。只是文档中的下几行说:
此规则允许协变返回类型 - 在覆盖方法时优化方法的返回类型。 如果R1不是R2的子类型,则会发生编译时未经检查的警告 除非被SuppressWarnings注释(第9.6.3.5节)压制。
清除。 R1必须是R2的子类型,Integer
与另一个Integer
或Number
相同。 String
不是void
的子类型。
文档说:“编译时未经检查的警告发生......”。但我注意到的是一个完整的编译错误等待着。
继承仍然按预期工作。删除您的子方法,您可以访问父级,静态或其他方式。