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

时间:2015-06-16 18:10:21

标签: java jls

由于StringstaticMethod的{​​{1}}返回类型,此代码无法编译。

Child

我知道§8.4.8.3中的JLS 8,“覆盖和隐藏的要求”说:

  

如果返回类型为R1的方法声明d1覆盖或隐藏   声明另一个方法d2,返回类型为R2,则d1必须为   d2的return-type-substitutable(第8.4.5节),或编译时错误   发生。

我的问题是在静态方法的特定情况下进行编译时检查的动机是什么,这个例子说明在编译期间未能进行此验证会产生任何问题是理想的。

3 个答案:

答案 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设计人员必须小心并理解潜在的危险用法。如果AB来自同一作者,并且用户无法对其进行子类化,则此设计非常安全。

答案 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的数量。 ChildParent,因此理想情况下Child应该知道如何计算自身的实例。如果static成员未被继承,则您必须在Parent内复制Child中的代码。

答案 2 :(得分:0)

我认为编译器如何对待你,你的方法是静态的还是其他方面都没有错。只是文档中的下几行说:

  

此规则允许协变返回类型 - 在覆盖方法时优化方法的返回类型。   如果R1不是R2的子类型,则会发生编译时未经检查的警告   除非被SuppressWarnings注释(第9.6.3.5节)压制。

清除。 R1必须是R2的子类型,Integer与另一个IntegerNumber相同。 String不是void的子类型。

文档说:“编译时未经检查的警告发生......”。但我注意到的是一个完整的编译错误等待着。

继承仍然按预期工作。删除您的子方法,您可以访问父级,静态或其他方式。