【问题标题】:Why is it not allowed to give the same name to a super class final method and a sub class method? [duplicate]为什么不允许超类final方法和子类方法同名? [复制]
【发布时间】:2021-07-29 14:12:02
【问题描述】:

在Java中,无法在与超类中的final方法同名的子类中编写方法的实际原因是什么? (请注意,我并没有试图覆盖该方法,这就是我放置关键字final的原因。)

请看下面的例子:

class A {
    public final void method() {
        System.out.println("in method A");
    }
}

class B extends A {
    public void method() {
        System.out.println("in method B");
    }
}

问题在IDE中表现为“'method()' cannot override 'method()' in 'A'; overridden method is final”;但是,我想了解导致编译器失败的这种情况的原因。

【问题讨论】:

  • 因为您已经在超类中创建了method() final,这意味着如果您尝试覆盖它,您希望编译器失败。
  • “我并不想重写该方法” - 但你仍然这样做
  • 为什么?因为超类保证类的所有用户method 将完全按照指定的方式运行,并且它明确地防止覆盖。如果你现在重写它,你可能会违反它的内部不变性,破坏它的状态、行为、接口契约等等。
  • 您是说您没有覆盖,但您正在做的是方法覆盖和使用最终方法,这是不允许的。

标签: java overriding final


【解决方案1】:

因为在 java 中,覆盖不是可选的。

类级别的方法名称。

在类级别(例如,类文件中的内容以及 JVM 执行的内容),方法名称​​包括它们的返回类型和参数类型(当然还有名称)。在 JVM 级别,不存在 varargs(它是一个数组),不存在泛型(出于签名目的将它们删除),并且throws 子句不是故事的一部分。但除此之外,这个方法:

public void foo(String foo, int bar, boolean[] baz, long... args) throws Exception {}

在类文件级别变成这个名字:

foo(Ljava/lang/String;I[Z[J)V

这看起来像 gobbledygook,但[ 是“数组”,每个基元都有一个字母(Z 代表布尔值,J 代表长整数,I 代表整数),V 代表 void,L 代表:对象类型.现在说得通了。

这实际上是类级别的方法名称(嗯,我们称之为签名)。 ANY 调用 java 中的方法,在类级别,总是使用完整的签名。这意味着javac 根本无法编译方法调用,除非它实际上知道您正在调用的确切方法,这就是为什么javac 不起作用,除非您在编译时拥有您调用的所有内容的完整类路径。

覆盖不是可选的!

在类级别,如果您定义的方法的完整签名与父类中的签名完全匹配,则它会覆盖该方法。时期。你不能不。 @Override 作为注解丝毫不会影响这一点(如果您没有覆盖任何内容,该注解只会导致编译器抱怨,它是经过编译器检查的文档,仅此而已)。

javac 更进一步

作为一个语言的东西,javac 会在你想收紧返回类型时搭建桥梁。给定:

class Parent {
    Object foo() { return null; }
}

class Child extends Parent {
    String foo() { return null; }
}

然后在类级别,Parent 中的一个方法的完整签名是foo()Ljava/lang/Object;,而 Child 中的一个方法具有 foo()Ljava/lang/String;,因此这些方法不同,并且 Child 的 foo 似乎不会覆盖 Parent 的富。

但是javac 介入,并且确实使这些覆盖。它通过在 Child 中实际创建 2 个方法 来做到这一点。你可以看到这一点!编写上面的代码,编译它,然后在 Child 上运行 javap -c -v,你会看到这些。 javac 有 2 个方法:foo()Ljava/lang/String;foo()Ljava/lang/Object;(它们确实具有相同的签名,因此根据定义覆盖了 Parent 的实现)。第二个实现为仅调用“真实”foo(返回字符串),并获得synthetic 标志。

决赛就是这样

这最终解决了你的问题:鉴于final 说:我不能被覆盖,那么,就是这样。您现在已经制定了 2 条互斥规则:

  • 父母的 foo 不能被覆盖
  • 根据定义(因为其签名匹配),孩子的 foo 会覆盖父母的 foo

Javac 将在此结束,在您的脸上抛出一个错误,然后收工。如果您想象一些假设的 javac 更新,这些因素的组合应该导致 javac 创建一个单独的方法:但是,如何?在类级别,相同的签名 == 相同的方法(这是一个覆盖),那么你建议什么?那个java在名字后面加了0

如果是这样,javac应该如何处理:

Parent p = new Child();
p.foo();

那里是哪个foofoo()Ljava/lang/Object; 来自父母,还是foo0()L/java/Object; 来自孩子?

您可以编写一个规范来回答这个问题(大概,这里很明显:Parent's foo;你是否写过Child c = new Child(); c.foo(); 然后foo0 是有意的,但这使得语言变得相当复杂,以及用于什么目的?

Java 语言设计者认为这不是一个有用的练习,因此没有将这种复杂性添加到语言中。我很确定这显然是正确的选择,但您的意见当然可能不同。

【讨论】:

    【解决方案2】:

    Final 不仅意味着您无法覆盖它,还意味着您无法解决调用该方法的问题。

    当您对对象进行子类化时,如果您可以创建一个隐藏该最终方法的方法,那么您可以阻止超类方法运行,或者替代对象用户所期望的其他功能。这将允许引入恶意代码,并会破坏使方法最终确定的目的。

    在您的情况下,听起来使超类方法 final 可能不是最佳选择。

    【讨论】:

      猜你喜欢
      • 2021-07-20
      • 2022-01-19
      • 1970-01-01
      • 2014-06-20
      • 2023-04-05
      • 1970-01-01
      • 2017-10-08
      • 1970-01-01
      相关资源
      最近更新 更多