Inheritance and recursion

JavaInheritanceRecursion

Java Problem Overview


Suppose we have the following classes:

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

}

Now lets call recursive in class A:

public class Demo {

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

}

The output is, as expected counting down from 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)

Let's get to the the confusing part. Now we call recursive in class B.

Expected:

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)

Actual:

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

How does this happen? I know this is a devised example, but it makes me wonder.

Older question with a [concrete use case][1].

[1]: https://stackoverflow.com/questions/10357029/java-inheritance-and-recursion "Older question"

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

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

Solution 3 - Java

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.

Solution 4 - Java

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.

Solution 5 - Java

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.

Solution 6 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionraupachView Question on Stackoverflow
Solution 1 - JavaTunakiView Answer on Stackoverflow
Solution 2 - JavaakashView Answer on Stackoverflow
Solution 3 - JavaErick G. HagstromView Answer on Stackoverflow
Solution 4 - JavaEranView Answer on Stackoverflow
Solution 5 - JavaEpicPandaForceView Answer on Stackoverflow
Solution 6 - JavajonrsharpeView Answer on Stackoverflow