继承和递归

假设我们有以下类:

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);
}


}

现在让我们调用 A 类中的 recursive:

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)

让我们进入令人困惑的部分。现在我们在 B 类中调用 recursive

预期 :

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...

这是怎么发生的? 我知道这是一个设计好的例子,但这让我很好奇。

老问题,带 具体用例具体用例

5748 次浏览

super.recursive(i + 1); in class B calls the super class's method explicitly, so recursive of A is called once.

Then, recursive(i - 1); in class A would call the recursive method in class B which overrides recursive of class A, since it is executed on an instance of class B.

Then B's recursive would call A's recursive explicitly, and so on.

Because recursive(i - 1); in A refers to this.recursive(i - 1); which is B#recursive in second case. So, super and this will be called in recursive function alternatively.

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

in A

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

This is expected. This is what happens for an instance of 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
}


}

As such, the calls are alternating between A and B.

This doesn't happen in the case of an instance of A because the overriden method won't be called.

When a B instance's recursive method calls the superclass implementation, the instance being acted on is still of B. Therefore when the super class's implementation calls recursive without further qualification, that's the subclass implementation. The result is the never-ending loop you're seeing.

That actually cannot go any other way.

When you call B.recursive(10);, then it prints B.recursive(10) then calls the implementation of this method in A with i+1.

So you call A.recursive(11), which prints A.recursive(11) which calls the recursive(i-1); method on the current instance which is B with input parameter i-1, so it calls B.recursive(10), which then calls the super implementation with i+1 which is 11, which then recursively calls the current instance recursive with i-1 which is 10, and you'll get the loop that you see here.

This is all because if you call the method of the instance in the superclass, you'll still call the implementation of the instance on which you're calling it.

Imagine this,

 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();
}
}

You'll get "BARK" instead of a compilation error such as "the abstract method cannot be called on this instance" or a runtime error AbstractMethodError or even pure virtual method call or something like that. So this is all to support polymorphism.

The other answers have all explained the essential point, that once an instance method is overridden it stays overridden and there's no getting it back except through super. B.recursive() invokes A.recursive(). A.recursive() then invokes recursive(), which resolves to the override in B. And we ping pong back and forth until the end of the universe or a StackOverflowError, whichever comes first.

It would be nice if one could write this.recursive(i-1) in A to get its own implementation, but that would probably break things and have other unfortunate consequences, so this.recursive(i-1) in A invokes B.recursive() and so forth.

There is a way to get the expected behavior, but it requires foresight. In other words, you must know in advance that you want a super.recursive() in a subtype of A to get trapped, so to speak, in the A implementation. It is done like so:

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);
}
}

Since A.recursive() invokes doRecursive() and doRecursive() can never be overridden, A is assured that it is calling its own logic.