Reputation: 3102
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.
Upvotes: 86
Views: 6131
Reputation: 4945
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.
Upvotes: 27
Reputation: 81539
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.
Upvotes: 16
Reputation: 121987
When a B
instance's recursive
method calls the super
class 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.
Upvotes: 14
Reputation: 137064
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.
Upvotes: 76
Reputation: 22972
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
}
}
Upvotes: 29
Reputation: 393781
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.
Upvotes: 16