java final方法vs c ++非虚函数

时间:2011-04-18 04:46:45

标签: java c++ final non-virtual-interface

java final方法和c ++非虚方法有什么不同或相同?怎么样?

5 个答案:

答案 0 :(得分:6)

他们是不同的。

  • 不会调度C ++非虚方法,也不会覆盖任何内容。

  • 调度Java final方法,并可以覆盖其类超类中的方法。

但是,它们的相似之处在于无法覆盖C ++非虚方法或Java最终方法。它们也是相似的,如果你有一些静态类型是有问题类型的对象,运行时系统不会需要来调度方法调用。

为了说明差异,请考虑以下两个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.

在B ++ toString()非虚拟的C ++等价物中,我认为a.toString()无法发送到B::toString()。 (我的C ++上有点生疏......)


(事实上,Java JIT编译器能够检测不需要虚拟调度的情况...... 没有你将类或方法声明为final。因此,真正的目的final指定不应该覆盖方法或不扩展类...并让Java编译器为您检查。)

答案 1 :(得分:4)

您仍然可以在C ++中继承类时使用相同的签名声明非虚拟成员函数,其中,Java明确禁止声明具有相同签名的方法,其中基类声明该方法最终。 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;
}

使用final的Java中的可比示例将导致编译器错误:

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: Polymorphism

但是,有效的是,两种方法都有类似的目标:防止覆盖基类中的函数。他们只是以稍微不同的方式去做。

答案 2 :(得分:2)

他们是非常不同的,事实上,我会说,完全不相关。

在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 ++中,没有什么可以阻止您在Derived类中使用相同的名称创建另一个函数(请注意,不同的签名仍然会导致隐藏所有具有相同名称的基类成员函数)。您将获得的是编译器警告,告诉您Derived类的成员函数隐藏了Base类的成员函数。

Java中的最终成员函数完全不同。在Java中,所有成员函数都是虚拟的。因此,您无法像在C ++中那样关闭虚拟调度。最终成员函数意味着不允许任何后续派生类(将发生错误)来声明具有相同名称(和签名)的成员函数。但是,在声明原始成员函数的接口/基类和将其标记为final的派生类之间仍然会发生虚拟调度(因此,在动态多态意义上覆盖)。只是稍后重写该函数是严格禁止的(即在Base类中使用标记为final的foo()尝试上面的代码会在Derived类的声明中出错,因为foo()不会出现允许的)。

如您所见,这两个概念完全不同。

  • 在C ++中,使用非虚拟成员函数,不会发生虚拟调度(因此,传统意义上没有“覆盖”),但是您可以使用具有相同名称的成员函数的派生类(并且它可以在“静态多态”中有时很有用。)
  • 在Java中,使用最终成员函数,仍然会发生虚拟调度,但严格禁止在后续派生类中重写。

答案 3 :(得分:0)

使用虚拟与非虚拟功能是C ++可以产生性能差异,相反,Java中可能没有性能差异。在Java中,将方法标记为final它纯粹是关于代码的清晰度和可维护性(它不是默认行为并且相对很少使用),并且在C ++中非虚函数是默认行为并且通常部分使用因为它们具有更好的性能特征。

在Java中,生成的代码可能因使用方式而有所不同,而C ++必须在编译时生成正确性。

e.g。如果JVM检测到“虚拟”方法只有一个或两个常用的实现,它可以内联这些方法或处理“虚拟”方法,只使用一个实现,就好像它是最终的一样。

答案 4 :(得分:-1)

有效,是的。在C ++中,声明一个虚函数是这样的,你可以覆盖它,默认情况下它们是非虚拟的,也就是最终的。

在Java中,情况正好相反。如果未指定,则子类可以覆盖函数,但必须将它们声明为final以防止这种情况。

如果您对JVM如何实现final感到好奇,那么函数引用是否确保它实际上引用了超类内存位置上的相同代码本身,就像在C ++中非虚拟引用那样,不能保证。 JVM可以优化他们认为合适的方式,但主要是,最终是这样编译器可以阻止你覆盖超类的最终方法开始。