86

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.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
raupach
  • 3,092
  • 22
  • 30
  • 1
    The keyword you need is static and dynamic types! you should search for that and read a bit about it. – ParkerHalo Dec 30 '15 at 14:17
  • 5
    You are running afool of Java's virtual-by-default. [How is defining that a method can be overridden a stronger commitment than defining that a method can be called?](http://programmers.stackexchange.com/questions/304270/how-is-defining-that-a-method-can-be-overridden-a-stronger-commitment-than-defin) – Deduplicator Dec 30 '15 at 16:58
  • 1
    To get your desired results extract the recursive method to a new private method. – Onots Dec 30 '15 at 16:59
  • 1
    @Onots I think making the recursive methods static would be cleaner. – ricksmt Dec 30 '15 at 17:44
  • 1
    It's simple if you notice that the recursive call in `A` is actually *dynamically dispatched* to the the `recursive` method of the current object. If you are working with an `A` object, the call takes you to `A.recursive()`, and with a `B` object, to `B.recursive()`. But `B.recursive()` always calls `A.recursive()`. So, if you start a `B` object, it switches back and forth. – LIProf Dec 30 '15 at 23:22

6 Answers6

76

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.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
29

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
    }
}
akash
  • 22,664
  • 11
  • 59
  • 87
27

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.

Erick G. Hagstrom
  • 4,873
  • 1
  • 24
  • 38
  • I wonder, why calling `doRecursive()` inside `recursive()` from the object `B` works. As TAsk wrote in his answer a function call works like `this.doRecursive()` and the Object `B` (`this`) has no method `doRecursive()` because it is in class `A` defined as `private` and not `protected` and will therefore be not inherited, right? – Timo Denk Jan 05 '16 at 22:16
  • 1
    Object `B` can't call `doRecursive()` at all. `doRecursive()` is `private`, yes. But when `B` calls `super.recursive()`, that invokes the implementation of `recursive()` in `A`, which has access to `doRecursive()`. – Erick G. Hagstrom Jan 06 '16 at 08:45
  • 2
    This is exactly the approach Bloch recommends in Effective Java if you absolutely must allow inheritance. Item 17: "If you feel you must allow inheritance from [a concrete class that doesn't implement a standard interface], one reasonable approach is to ensure that the class never invokes any of its overridable methods and to document the fact." – Joe Jan 25 '16 at 20:05
16

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.

Eran
  • 387,369
  • 54
  • 702
  • 768
16

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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
14

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.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437