为什么所有的java方法都是隐式可重写的?

2022-09-02 02:24:31

在C++,我必须显式指定“virtual”关键字以使成员函数“可重写”,因为当成员函数可重写时,存在创建虚拟表和vpointers的开销(因此由于性能原因,每个成员函数都隐式不可重写)。

它还允许在子类提供具有相同名称和签名的单独实现时隐藏(如果未覆盖)成员函数。

C# 中也使用相同的技术。我想知道为什么Java放弃了这种行为,并使每个方法在默认情况下都是可重写的,并提供了在显式使用“final”关键字时禁用覆盖行为的功能。


答案 1

更好的问题可能是“为什么C#有非虚拟方法?或者至少,为什么默认情况下它们不是虚拟的,可以选择将它们标记为非虚拟的?

在C++中,有一个想法(正如布莱恩很好地指出的那样),如果你不想要它,你就不需要为它付钱。问题是,如果你确实想要它,这通常意味着你最终会为此付出代价。在大多数Java实现中,它们是为许多虚拟调用显式设计的;vtable实现往往很快,几乎不比非虚拟调用昂贵,这意味着非虚拟功能的主要优势丢失了。此外,JIT 编译器可以在运行时内联虚函数。因此,出于效率原因,实际上几乎没有理由使用非虚函数。

因此,它在很大程度上归结为最小意外原则。它告诉我们,所有方法都以相同的方式运行,而不是一半是虚拟的,一半是非虚拟的。由于我们至少需要一些虚拟方法来实现这种多态性,因此将它们全部设置为虚拟是有意义的。此外,拥有两种具有相同签名的方法只是要求向自己开枪。

多态性还规定对象本身应该控制它的作用。它的行为不应该取决于客户认为它是FooParent还是FooChild。

编辑:所以我被要求断言。接下来的这一段是我的推测,而不是对事实的陈述。

所有这一切的一个有趣的副作用是,Java程序员倾向于非常频繁地使用接口。由于虚拟方法优化使得接口的成本基本上不存在,因此它们允许您使用List(例如)而不是ArrayList,并在以后的某个日期将其切换为LinkedList,只需进行简单的一行更改,就不会有额外的惩罚。

编辑:我也会小马驹几个来源。虽然不是原始来源,但它们确实来自Sun,解释了HotSpot的一些工作原理。

内联

VTable


答案 2

从这里开始 (#34)

Java 中没有 virtual 关键字,因为所有非静态方法始终使用动态绑定。在Java中,程序员不必决定是否使用动态绑定。虚拟之所以存在于C++中,是因为当你在调整性能时,你可以把它关掉,以略微提高效率(或者换句话说,“如果你不使用它,你就不付钱”),这通常会导致混乱和不愉快的惊喜。final 关键字为效率调优提供了一些自由度 - 它告诉编译器此方法不能被覆盖,因此它可以静态绑定(并且内联,因此使用等效于C++非虚拟调用)。这些优化取决于编译器。

也许有点循环。


推荐