当前位置: 首页 > 知识库问答 >
问题:

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

陶智
2023-03-14

代码将无法编译,因为中的静态方法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),否则会发生编译时错误。

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

共有3个答案

鲁向明
2023-03-14

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

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

清楚。R1必须是R2的子类型,就像整数是另一个整数数字字符串不是无效的子类型。

文档说:"编译时未检查的警告发生..."。但是我注意到一个成熟的编译错误正在等待。

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

呼延运恒
2023-03-14
匿名用户

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最好也知道如何计算自身的实例。如果未继承静态成员,则必须在中复制中的代码。

董谦
2023-03-14

这是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()-

当JVM执行此代码时,它首先查找方法foo()-

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

public class B extends A
{
    public static Number foo(){ return 0.2f; }
}

我们从B获得了新的二进制文件,然后再次运行我们的应用程序。(请注意,C的二进制文件保持不变;它没有针对新的B重新编译)塔达C.x现在在运行时是0.2f!!因为JVM正在搜索foo()-

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

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

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

public class B extends A
{
    public static Integer foo(){ return 42; }
}

据B所知,这可以很好地编译A。Java允许这样做,因为返回类型是“协变”的;这个特性相对较新;以前,“重写”静态方法必须具有相同的返回类型。

那么这次C.x将是什么?它是0.1f!因为JVM找不到foo()-

如果根据最新的代码重新编译C,C的二进制文件将引用B. foo()-

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

这一团糟表明,允许静态方法具有“协变返回类型”是一个设计疏忽,这实际上只是针对实例方法的。

更新-此功能在某些用例中可能很有吸引力,例如,静态工厂方法-A.of(…) 返回A,而B.of(..) 返回更具体的B。API设计者必须小心,并对潜在的危险用法进行推理。如果AB来自同一作者,并且用户不能对它们进行子分类,那么这种设计是非常安全的。

 类似资料:
  • 我理解不同的行为(与覆盖相比是新的),但不理解术语。

  • Test类是超级类,Fest是它的子类,因为我们知道静态方法不能被重写,即使我遇到了“静态方法不能在java中隐藏实例方法”这样的错误,有人能解释一下吗,提前谢谢。

  • 问题内容: 我正在为某门课程的一些代码编写解释,并且偶然使用了这些单词并且可以互换使用。我决定回过头来修正措辞,但在我的理解上遇到了一个漏洞。 据我了解,子例程是一个如果它不作用于一个类的实例(其作用仅限于其显式输入/输出),并且是一个如果它作用于一个类的实例(它可能带有消除导致实例不纯的副作用)。 这里有一个很好的讨论主题。请注意,根据接受的答案的定义,静态实际上应该是一个函数,因为永远不会隐式

  • 我看过返回IList vs ICollection vs Collection以及它链接的其他问题,但我仍然对这个问题感到困惑。 出于演示目的,我们假设我有一个类,在其中公开了一个公共方法,如下所示: 要遵循CA1002,我的方法应该返回实际的集合类(、等)或它们的接口(、等),如果我希望返回具体?

  • 下面对getHighest()和getLowest()的调用返回Comparable类型的对象,而不是T类型的对象,这正是我们想要的。为什么,我该如何改进这段代码,使这些调用返回T(这样T的字段和方法就可用了)? 下一行生成编译器错误: 错误:找不到符号符号:方法getName()位置:接口java.lang.Comparable 我想employee.getHighest()返回一个员工(而不仅

  • 问题内容: 可能是之前问过的一个问题,但是像往常一样,第二个提到“通用”一词时,您会得到一千个答案,以解释类型擦除。我很早以前就经历了这一阶段,现在对泛型及其使用有了很多了解,但是这种情况稍微有些微妙。 我有一个表示电子表格中数据单元格的容器,该容器实际上以两种格式存储数据:既作为显示字符串,也取决于数据(作为对象存储)为另一种格式。该单元格还包含一个在类型之间转换的转换器,并且还对类型进行有效性