在 C++ 中,我必须显式指定“虚拟”关键字以使成员函数“可覆盖”,因为当成员函数可覆盖时,会产生创建虚拟表和 vpointer 的开销(因此每个成员函数对于性能原因)。
当子类提供具有相同名称和签名的单独实现时,它还允许隐藏成员函数(如果未覆盖)。
C# 中也使用了相同的技术。我想知道为什么 Java 放弃了这种行为,并且默认情况下使每个方法都可以覆盖,并提供了在显式使用“final”关键字时禁用覆盖行为的能力。
在 C++ 中,我必须显式指定“虚拟”关键字以使成员函数“可覆盖”,因为当成员函数可覆盖时,会产生创建虚拟表和 vpointer 的开销(因此每个成员函数对于性能原因)。
当子类提供具有相同名称和签名的单独实现时,它还允许隐藏成员函数(如果未覆盖)。
C# 中也使用了相同的技术。我想知道为什么 Java 放弃了这种行为,并且默认情况下使每个方法都可以覆盖,并提供了在显式使用“final”关键字时禁用覆盖行为的能力。
更好的问题可能是“为什么 C# 有非虚拟方法?” 或者至少,为什么默认情况下它们不是虚拟的,并且可以选择将它们标记为非虚拟?
在 C++ 中,有一个想法(正如 Brian 很好地指出的那样),如果你不想要它,你就不用付钱。问题是,如果你确实想要它,这通常意味着你最终会为此付出代价。在大多数 Java 实现中,它们是为大量虚拟调用而明确设计的;vtable 实现往往很快,几乎不比非虚拟调用贵,这意味着失去了非虚拟函数的主要优势。此外,JIT 编译器可以在运行时内联虚函数。因此,出于效率原因,实际上几乎没有理由使用非虚拟函数。
因此,它在很大程度上归结为最小意外原则。它告诉我们所有方法的行为方式都相同,不是一半是虚拟的,一半是非虚拟的。由于我们至少需要一些虚拟方法来实现这种多态性,所以让它们都是虚拟的是有意义的。此外,具有相同签名的两种方法只是要求在脚上开枪。
多态性还规定对象本身应该可以控制它所做的事情。它的行为不应该取决于客户认为它是 FooParent 还是 FooChild。
编辑:所以我被要求发表我的主张。下一段是我的推测,而不是事实陈述。
所有这一切的一个有趣的副作用是 Java 程序员倾向于大量使用接口。由于虚方法优化使接口的成本基本上不存在,它们允许您使用 List(例如)而不是 ArrayList,并在以后通过简单的一行更改将其切换为 LinkedList没有额外的罚款。
编辑:我还会提供一些资源。虽然不是原始来源,但它们确实来自 Sun,解释了 HotSpot 的一些工作原理。
取自这里(#34)
Java 中没有 virtual 关键字,因为所有非静态方法总是使用动态绑定。在 Java 中,程序员不必决定是否使用动态绑定。C++ 中存在 virtual 的原因是,当您调整性能时,您可以将其关闭以稍微提高效率(或者,换句话说,“如果您不使用它,您就不会为此付费” ),这通常会导致混乱和令人不快的意外。final 关键字为效率调整提供了一些空间——它告诉编译器这个方法不能被覆盖,因此它可以被静态绑定(并且被内联,因此使用相当于 C++ 非虚拟调用)。这些优化取决于编译器。
可能有点圆。
所以 Java 的基本原理可能是这样的:面向对象语言的全部意义在于可以扩展事物。所以就纯粹的设计而言,将可扩展性视为“特例”确实没有什么意义。
请记住,Java 具有在运行时编译的优势。因此,C++ 编译中的一些性能参数被忽略了。在 C++ 中,如果一个类可能被覆盖,那么编译器必须采取额外的步骤。在 Java 中,这并不神秘:在任何给定的时刻,JVM 都知道某个特定的方法/类是否已被覆盖,而这才是最重要的。
请注意,final关键字本质上是关于程序设计,而不是优化。JVM 不需要这些信息来查看类/方法是否已被覆盖!!
如果问题是要问 java 和 C++/C# 之间更好的方法是什么,那么它已经在另一个线程中以相反的方向讨论过,并且网上有很多资源可用
http://www.artima.com/intv/nonvirtual.html
最近引入的@Override 注释及其在新代码中的广泛采用表明了“为什么所有 java 方法都是隐式可覆盖的?”这个问题的确切答案。确实是因为设计师搞错了。(他们已经修好了)
哦 !我会为此投反对票。
Java 试图向更动态的语言定义靠拢,在这种定义中,一切都是对象,一切都是虚拟方法。它还希望避免模棱两可和难以理解的构造,它的设计者将其视为 C++ 中的一个缺陷,因此没有运算符重载,在这种情况下,不能在一个类层次结构上拥有两个公共方法签名,根据类型调用不同的方法引用它的变量。
C# 更关心子类的稳定性,并确保子类的行为可预测。C++ 关心性能。
三种不同的设计重点,导致不同的选择。
我想说的是,与整个 VM 成本相比,虚拟方法的 Java 成本较低。与类似汇编的 C 背景相比,在 C++ 中,它的成本很高。由于 C 到 C++ 的迁移,没有人会决定默认通过指针调用所有方法。变化太大了。