在C ++中,我必须明确指定'virtual'关键字以使成员函数'overridable',因为当成员函数被覆盖时会产生创建虚拟表和vpointers的开销(因此每个成员函数都是隐式的)因性能原因而无法覆盖)。
当子类提供具有相同名称和签名的单独实现时,它还允许隐藏成员函数(如果未覆盖)。
在C#中也使用相同的技术。我想知道为什么Java摆脱了这种行为并且默认情况下使每个方法都可以覆盖,并提供了在显式使用'final'关键字时禁用覆盖行为的能力。
答案 0 :(得分:27)
更好的问题可能是“为什么C#有非虚方法?”或者至少,为什么默认情况下它们不是虚拟的,可以选择将它们标记为非虚拟?
在C ++中,有一个想法(正如Brian所指出的那样),如果你不想要它,你就不用付钱了。问题在于,如果你真的想要它,这通常意味着你最终会为此付出代价。在大多数Java实现中,它们是为大量虚拟调用而明确设计的; vtable实现往往比非虚拟调用快,几乎不贵,这意味着非虚函数的主要优点就会丢失。此外,JIT编译器可以在运行时内联虚拟函数。因此,出于效率原因,实际 使用非虚函数的理由很少。
因此,它主要归结为最不惊讶的原则。它告诉我们所有方法都以相同的方式运行,其中一半是虚拟的,一半是非虚拟的。由于我们需要至少有一些虚拟方法来实现这种多态性,所以将它们都设置为虚拟是有意义的。此外,有两种具有相同签名的方法只是要求自己在脚下射击。
多态性也决定了对象本身应该控制它的作用。它的行为不应该决定客户端是认为它是FooParent还是FooChild。
编辑:所以我被称为我的断言。下一段是我的推测,而不是事实陈述。所有这些有趣的副作用是Java程序员倾向于非常频繁地使用接口。由于虚方法优化使得接口的成本基本上不存在,它们允许您使用List(例如)而不是ArrayList,并在稍后的某个日期通过简单的单行更改将其切换为LinkedList。没有额外的惩罚。
编辑:我还会提供一些消息来源。虽然不是原始资源,但它们确实来自Sun,解释了HotSpot上的一些工作原理。答案 1 :(得分:19)
取自here(#34)
Java中没有虚拟关键字 因为所有非静态方法总是如此 使用动态绑定。在Java中, 程序员不必决定 是否使用动态绑定。该 在C ++中存在虚拟的原因是如此 可以让它稍微增加 在你进行调整时的效率 表现(或换句话说,“如果 你不使用它,你不付钱 它“),往往导致混乱 和不愉快的惊喜。决赛 关键字提供了一些自由度 效率调整 - 它告诉了 编译器认为这种方法不可能 被覆盖,因此它可能是 静态绑定(并且内联, 因此使用相当于C ++ 非虚拟电话)。这些优化 取决于编译器。
或许有点循环。
答案 2 :(得分:11)
因此,Java的基本原理可能是这样的:面向对象语言的全部意义在于可以扩展事物。因此,就纯粹的设计而言,将可扩展性视为“特殊情况”确实没有意义。
请记住,Java可以在运行时进行编译。因此,C ++编译中的一些性能参数就会消失。在C ++中,如果一个类可能被覆盖,那么编译器必须采取额外的步骤。在Java中,没有任何关于它的谜团:在任何给定的时刻, JVM知道某个特定的方法/类是否已被覆盖,这基本上是重要的是什么。
请注意,最终关键字主要是关于程序设计,而不是优化。 JVM不需要此信息来查看是否已重写类/方法!!
答案 3 :(得分:3)
如果问题是要问java和C ++ / C#之间的更好的方法,那么它已经在另一个线程中以相反的方向进行了讨论,并且网上有许多可用的资源
Why C# implements methods as non-virtual by default?
http://www.artima.com/intv/nonvirtual.html
@Override注释的最新介绍及其在新代码中的广泛采用,提出了“为什么所有java方法都可以隐式覆盖?”这一问题的确切答案。确实是因为设计师犯了一个错误。 (他们已经修好了)
哦,哦!我将对此投反对票。答案 4 :(得分:1)
Java试图更接近更动态的语言定义,其中一切都是对象,一切都是虚方法。它还希望避免歧义和难以理解的构造,它被设计者视为C ++中的一个缺陷,因此没有运算符重载,并且在这种情况下无法在一个类层次结构上具有两个公共方法签名,根据类型调用不同的方法引用它的变量。
C#更关心子类的稳定性,并确保子类的行为可预测。 C ++关注性能。
三种不同的设计优先级,导致不同的选择。
答案 5 :(得分:1)
我会说,与整个虚拟机成本相比,虚拟方法的Java成本较低。与类似程序集的C背景相比,在C ++中,它的成本很高。由于C到C ++的迁移,没有人会决定默认通过指针调用所有方法。这是一个太大的变化。