Java的final方法和c++的非虚方法是不同的还是相同的?如何?
它们是不同的。
-
c++的非虚方法不会被分派,也不会覆盖任何东西。
-
Java的最终方法是被分派的,并且可以覆盖它们类的父类中的方法。
public class A {
public String toString() {
return "A";
}
}
public class B extends A {
public final String toString() {
return "B";
}
}
A a = ...
B b = ...
a.toString(); // could call A.toString() or B.toString() - dispatch
b.toString(); // could only call B.toString() - no dispatch required
// but it will behave the same as if dispatching had occurred.
在等效的c++中,B::toString()是非虚拟的,我认为a.toString()
不能分派给B::toString()
。(我的c++有点生疏了…)
(事实上,Java JIT编译器能够检测不需要虚拟调度的情况…没有你声明类或方法为final
。因此,final
的真正目的是指定一个方法不能被重写或者一个类不能被扩展…
您仍然可以在c++中继承类时声明具有相同签名的非虚成员函数,而Java明确禁止声明具有与基类声明该方法final相同签名的方法。c++中的虚性只是帮助在处理继承/多态时找到要调用的正确函数。
的例子:
#include <iostream>
class Base
{
public:
void doIt()
{
std::cout << "from Base.doIt()" << std::endl;
}
};
class Child : public Base
{
public:
void doIt()
{
std::cout << "from Child.doIt()" << std::endl;
}
};
int main()
{
Base a;
a.doIt(); // calls Base.doIt()
Child b;
b.doIt(); // calls Child.doIt()
Base *c = new Base();
c->doIt(); // calls Base.doIt()
Child *d = new Child();
d->doIt(); // calls Child.doIt()
Base *e = new Child();
e->doIt(); // calls Base.doIt()
std::cin.ignore();
return 0;
}
在Java中使用final的类似示例将导致编译器错误:
public class Base
{
public final void doIt()
{
System.out.println("In Base.doIt()");
}
}
public class Child extends Base
{
public void doIt() // compiler error: Cannot overload the final method from Base
{
System.out.println("In Child.doIt()");
}
}
有关c++中多态性的更多说明,请参阅cplusplus.com:多态性
实际上,这两种方法都有相似的目标:防止在基类中重写函数。他们只是用了稍微不同的方式。它们非常不同,事实上,我想说,完全无关。
在c++中,如果基类具有非虚成员函数,则可以在派生类中声明同名的非虚成员函数。结果是派生类的成员函数将隐藏基类的成员函数。并且不会发生虚拟调度。如下所示:
struct Base {
void foo() {
std::cout << "Base::foo called!" << std::endl;
};
};
struct Derived : Base {
void foo() {
std::cout << "Derived::foo called!" << std::endl;
};
};
int main() {
Derived d;
d.foo(); //OUTPUT: "Derived::foo called!"
Base* b = &d;
b->foo(); //OUTPUT: "Base::foo called!"
};
上面展示了派生类的成员函数如何隐藏基类函数。如果你有一个指向基类的指针,因为函数是非虚的,虚表不用于解析调用,因此,基类中的foo函数将被调用。这里的要点是,在c++中,没有什么可以阻止您在派生类中创建具有相同名称的另一个函数(注意,不同的签名仍然会导致隐藏所有具有相同名称的基类成员函数)。你得到的只是一个编译器警告,告诉你派生类的成员函数隐藏了基类的成员函数。
Java中的final成员函数则完全不同。在Java中,所有成员函数都是虚函数。所以你不能像在c++中那样关闭虚拟调度。final成员函数只意味着不允许任何后续派生类(将发生错误)声明具有相同名称(和签名)的成员函数。但是,在声明原始成员函数的接口/基类和将其标记为final的派生类之间,仍然会发生虚拟分派(以及动态多态意义上的重写)。只是以后对函数的重写是严格禁止的(即,在基类中将foo()标记为final的情况下尝试上述代码会在派生类的声明中给出错误,因为那里的foo()是不允许的)。如你所见,这两个概念是完全不同的。
- 在c++中,对于非虚成员函数,不会发生虚调度(因此,没有传统意义上的"重写"),但允许具有同名成员函数的派生类(并且它有时在"静态多态性"中很有用)。
- 在Java中,对于final成员函数,仍然会发生虚拟调度,但严格禁止在后续派生类中重写。
在c++中使用虚函数和非虚函数会产生性能差异,相反在Java中可能没有性能差异。在Java中,将方法标记为final
纯粹是为了代码的清晰度和可维护性(它不是默认行为,相对较少使用),而在c++中,非虚函数是默认行为,通常使用部分是因为它们具有更好的性能特征。
在Java中,生成的代码可以根据它的使用方式而有所不同,而c++必须在编译时生成正确性。
。如果JVM检测到一个"虚拟"方法只有一个或两个常用的实现,它可以内联这些方法,或者将只使用一个实现的"虚拟"方法视为final。