为什么继承在Java和C ++中的行为方式不同,超类调用(或不调用)子类'方法?

时间:2018-04-12 11:40:56

标签: java c++ inheritance

我写过 - 似乎是 - 在Java和C ++中完全相同的继承示例。看到这些计划的不同产出,我感到非常惊讶。让我分享代码片段和相应的输出。

C ++代码:

class A
{
public:
    A() {}
    void sleep() {
        cout << "A.Sleep" << endl;
        eat();
    }
    void eat() {cout << "A.Eat" << endl;}
};

class B: public A
{
public:
    B() {}
    void sleep() {
        A::sleep();
        cout << "B.Sleep " <<endl;
        this->eat();
    }
    void eat() {
        cout << "B.Eat" << endl;
        run();
    }
    void run() {
        A::sleep();
        cout << "B.run" << endl;
    }
};

int main()
{
    B *b = new B();
    b->sleep();
}

输出:

A.Sleep
A.Eat
B.Sleep
B.Eat
A.Sleep
A.Eat
B.run

executed successfully...

Java代码:

class A
{
    A() {}
    void sleep() {
        System.out.println("A.Sleep");
        this.eat();
    }
    void eat() { System.out.println("A.Eat");}
};

class B extends A
{
    B() {}
    @Override
    void sleep() {
        super.sleep();
        System.out.println("B.Sleep");
        this.eat();
    }
    @Override
    void eat() {
        System.out.println("B.Eat");
        run();
    }
    void run() {
        super.sleep();
        System.out.println("B.Run");
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.sleep();
    }
}

输出:

A.Sleep
B.Eat
A.Sleep
B.Eat
A.Sleep
......
......
......
(Exception in thread "main" java.lang.StackOverflowError)

我不知道为什么这两个继承的例子表现不同。难道它的工作方式不同吗?我很想知道...... 这种情况的解释是什么?

2 个答案:

答案 0 :(得分:23)

在您的C ++示例中,您是hiding基本方法,但您不能覆盖它们。所以它们实际上是恰好具有相同名称的不同方法。如果你打电话

A* a = new B();
a->sleep();

它实际上会打印"A.Sleep"。如果要覆盖方法,则需要在Base类中声明它virtual(在所有子类中自动使其成为虚拟)。您可以在this post中阅读有关函数隐藏与覆盖C ++的更多信息。

在Java示例中,您实际上覆盖了方法,因此它们是相同的方法。一个取代旧的。您可以这样考虑:所有Java函数都被秘密标记为virtual,这意味着它们可以被覆盖。如果希望方法在Java中不可覆盖,则必须将其声明为final

答案 1 :(得分:4)

注意:要小心,每种语言都是思考的方式。有很多方法可以解释/实现OO。即使C ++和Java看起来很相似,它们也远非相似。

在这两种语言中,编译器验证在编译时,如果你可以通过检查一个方法(通过检查一个类(以及从当前一个继承的那个等)来获得一个方法)签名和可见性。让事情变得与众不同的是呼叫真正发出的方式。

<强> C ++

对于非虚方法,调用的方法完全由编译时确定。这就是为什么即使对象属于B类,当它执行A::sleep时,对eat的调用也会被解析为对A::eat的调用(eat不是虚拟的,然后编译器调用A::eat,因为你处于级别A)。在B::sleep()中,对this->eat()的通话被解析为对B.eat()的通话,因为该地点this的类型为B。您不能转到继承层次结构(在eat类中调用A永远不会在下面的类中调用eat方法。

请注意,虚拟方法的情况有所不同(它与Java情况更相似,但不同)。

<强>爪哇

在Java中,调用的方法是在运行时确定的,并且是与对象实例最相关的方法。因此,在A.sleep中,对eat的调用将是与当前对象类型相关的调用,这意味着类型为B(因为当前对象的类型为B然后会调用B.eat

然后你有一个堆栈溢出,因为当你正在玩B类型的对象时,对B.sleep()的调用会调用A.sleep(),这将调用B.eat(),反过来会调用B.run(),它会在永无止境的循环中调用A.sleep()等。