继承与递归


86

假设我们有以下类:

class A {

    void recursive(int i) {
        System.out.println("A.recursive(" + i + ")");
        if (i > 0) {
            recursive(i - 1);
        }
    }

}

class B extends A {

    void recursive(int i) {
        System.out.println("B.recursive(" + i + ")");
        super.recursive(i + 1);
    }

}

现在让我们打电话给recursiveA类:

public class Demo {

    public static void main(String[] args) {
        A a = new A();
        a.recursive(10);
    }

}

如预期的那样,输出从10开始递减。

A.recursive(10)
A.recursive(9)
A.recursive(8)
A.recursive(7)
A.recursive(6)
A.recursive(5)
A.recursive(4)
A.recursive(3)
A.recursive(2)
A.recursive(1)
A.recursive(0)

让我们进入令人困惑的部分。现在我们称为recursiveB类。

预期

B.recursive(10)
A.recursive(11)
A.recursive(10)
A.recursive(9)
A.recursive(8)
A.recursive(7)
A.recursive(6)
A.recursive(5)
A.recursive(4)
A.recursive(3)
A.recursive(2)
A.recursive(1)
A.recursive(0)

实际

B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...

这是怎么发生的?我知道这是一个设计好的示例,但这使我感到奇怪。

有一个具体用例的老问题。


1
您需要的关键字是静态和动态类型!您应该进行搜索并阅读一些有关它的信息。
ParkerHalo 2015年


1
为了获得所需的结果,请将递归方法提取为新的私有方法。
18年

1
@Onots我认为将递归方法设为静态会更干净。
ricksmt

1
如果您注意到递归调用A实际上是动态地分派recursive当前对象的方法,则很简单。如果使用的是A对象,则调用会将您带到A.recursive(),并使用一个B对象将对象带到B.recursive()。但是B.recursive()总是打电话A.recursive()。因此,如果启动一个B对象,则它会来回切换。
LIProf

Answers:


76

这是预期的。这是的实例发生的情况B

class A {

    void recursive(int i) { // <-- 3. this gets called
        System.out.println("A.recursive(" + i + ")");
        if (i > 0) {
            recursive(i - 1); // <-- 4. this calls the overriden "recursive" method in class B, going back to 1.
        }
    }

}

class B extends A {

    void recursive(int i) { // <-- 1. this gets called
        System.out.println("B.recursive(" + i + ")");
        super.recursive(i + 1); // <-- 2. this calls the "recursive" method of the parent class
    }

}

因此,呼叫在A和之间切换B

在实例的情况下不会发生这种情况,A因为不会调用重写的方法。


29

因为recursive(i - 1);Athis.recursive(i - 1);其为B#recursive在第二种情况。因此,super并且this将在递归函数中交替调用

void recursive(int i) {
    System.out.println("B.recursive(" + i + ")");
    super.recursive(i + 1);//Method of A will be called
}

A

void recursive(int i) {
    System.out.println("A.recursive(" + i + ")");
    if (i > 0) {
        this.recursive(i - 1);// call B#recursive
    }
}

27

其他答案都解释了要点,即一旦实例方法被覆盖,它将保持覆盖状态,除非通过,否则无法将其取回superB.recursive()调用A.recursive()A.recursive()然后调用recursive(),将解析为中的覆盖B。然后我们来回乒乓,直到宇宙的尽头或StackOverflowError,以先到者为准。

这将是很好,如果能写this.recursive(i-1)A拿到自己的实现,但是这可能会打破东西,并有其他不幸的后果,所以this.recursive(i-1)A所调用B.recursive()等等。

有一种获得预期行为的方法,但这需要先见之明。换句话说,您必须事先知道您希望super.recursive()子类中的aA被困在A实现中。它是这样完成的:

class A {

    void recursive(int i) {
        doRecursive(i);
    }

    private void doRecursive(int i) {
        System.out.println("A.recursive(" + i + ")");
        if (i > 0) {
            doRecursive(i - 1);
        }
    }
}

class B extends A {

    void recursive(int i) {
        System.out.println("B.recursive(" + i + ")");
        super.recursive(i + 1);
    }
}

由于A.recursive()调用doRecursive()并且doRecursive()永远不能被覆盖,因此A可以确保它正在调用自己的逻辑。


我想知道为什么从对象doRecursive()内部调用有效。正如TAsk在他的回答中所写,函数调用的工作方式类似于,而Object ()没有方法,因为它在类中定义为and ,因此不会被继承,对吗?recursive()Bthis.doRecursive()BthisdoRecursive()Aprivateprotected
Timo Denk

1
对象B根本无法调用doRecursive()doRecursive()private,是的。但是当B调用时super.recursive(),即调用recursive()in的实现,而inA可以访问doRecursive()
Erick G. Hagstrom

2
如果您绝对必须允许继承,那么这正是Bloch在Effective Java中建议的方法。第17项:“如果您认为必须允许从[未实现标准接口的具体类]继承,则一种合理的方法是确保该类从不调用任何可重写的方法并记录事实。”

16

super.recursive(i + 1);在类B调用父类的方法明确,这样recursiveA被调用一次。

然后,recursive(i - 1);在类A中将调用recursive在类B中重写recursiveclass的方法A,因为它是在class的实例上执行的B

随后Brecursive会叫Arecursive明确,等等。


16

实际上,这别无选择。

当你打电话B.recursive(10);,然后打印B.recursive(10)然后调用此方法的实现Ai+1

因此,您调用A.recursive(11),该命令会在具有输入参数的当前实例上打印A.recursive(11)出调用该recursive(i-1);方法的方法,因此它会调用,然后再使用来调用is的超级实现,然后该实例的递归调用使用is的当前实例。Bi-1B.recursive(10)i+111i-110,你会得到您在这里看到的循环。

这是因为如果您在超类中调用实例的方法,您仍将调用在其上调用实例的实现。

想象一下

 public abstract class Animal {

     public Animal() {
         makeSound();
     }

     public abstract void makeSound();         
 }

 public class Dog extends Animal {
     public Dog() {
         super(); //implicitly called
     }

     @Override
     public void makeSound() {
         System.out.println("BARK");
     }
 }

 public class Main {
     public static void main(String[] args) {
         Dog dog = new Dog();
     }
 }

您将得到“ BARK”,而不是诸如“无法在此实例上调用抽象方法”之类的编译错误,运行时错误AbstractMethodError或什pure virtual method call至类似的错误。因此,所有这些都支持多态


14

B实例的recursive方法调用super类实现时,作用的实例仍为B。因此,当超类的实现recursive无需进一步限定就调用时,即为子类实现。结果就是您看到的永无止境的循环。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.