为什么不允许给超类final方法和子类方法同名?

问题描述

在 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()'不能覆盖'A'中的'method()';被覆盖的方法是final的”;但是,我想了解导致编译器失败的情况是什么。

解决方法

因为在 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 代表空,L 代表:对象类型。现在说得通了。

这确实是类级别的方法名称,有效(好吧,我们称其为签名)。 任何在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 的 foo。

但是 javac 会进行干预,并且确实会覆盖这些内容。它通过在 Child 中实际制作 2 个方法来做到这一点。你可以看到这个在行动!编写上述内容,编译它,然后在 Child 上运行 javap -c -v,您会看到这些。 javac 有两种方法:foo()Ljava/lang/String;foo()Ljava/lang/Object;(它们具有相同的签名,因此根据定义覆盖了父级的实现)。第二个实现为仅调用“真实”foo(返回字符串的那个),并获取 synthetic 标志。

最终是什么

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

  • 父的 foo 不能被覆盖
  • 根据定义(因为它的签名匹配),Child 的 foo 会覆盖 Parent 的 foo

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

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

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

哪个 foo 用于那里? foo()Ljava/lang/Object; 来自家长,还是 foo0()L/java/Object; 来自孩子?

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

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

,

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

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

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