Héritage et récursion
supposons que nous ayons les classes suivantes:
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);
}
}
maintenant, appelons recursive
dans la classe A:
public class Demo {
public static void main(String[] args) {
A a = new A();
a.recursive(10);
}
}
la sortie est, comme prévu décompte à partir de 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)
passons à la partie la plus confuse. Maintenant nous appelons recursive
dans la classe B.
prévu :
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)
Réelle :
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...
comment cela se produit-il? Je sais que c'est un exemple inventé, mais je me pose la question.
Plus question avec un cas concret d'utilisation .
6 réponses
Ce qui est attendu. C'est ce qui se passe pour une instance de 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
}
}
comme tels, les appels alternent entre A
et B
.
cela ne se produit pas dans le cas d'une instance de A
parce que la méthode overriden ne sera pas appelée.
parce que recursive(i - 1);
dans A
fait référence à this.recursive(i - 1);
qui est B#recursive
dans le deuxième cas. Ainsi, super
et this
seront appelés dans recursive fonction alternativement .
void recursive(int i) {
System.out.println("B.recursive(" + i + ")");
super.recursive(i + 1);//Method of A will be called
}
dans A
void recursive(int i) {
System.out.println("A.recursive(" + i + ")");
if (i > 0) {
this.recursive(i - 1);// call B#recursive
}
}
les autres réponses ont toutes expliqué le point essentiel, qu'une fois qu'une méthode d'instance est dépassée, elle reste dépassée et il n'y a pas d'autre moyen de la récupérer que super
. B.recursive()
invoque A.recursive()
. A.recursive()
invoque alors recursive()
, qui se résout en B
. Et nous faisons des ping-pong jusqu'à la fin de l'univers ou un StackOverflowError
, selon ce qui vient en premier.
Ce serait bien si on pouvait écrire this.recursive(i-1)
dans A
pour obtenir sa propre mise en œuvre, mais ce serait sans doute de casser des choses et avoir d'autres conséquences fâcheuses, donc this.recursive(i-1)
dans A
invoque B.recursive()
et ainsi de suite.
Il y a un moyen d'obtenir le comportement attendu, mais il nécessite de prévoir. En d'autres termes, vous devez savoir à l'avance que vous voulez un super.recursive()
dans un sous-type de A
pour être piégé, pour ainsi dire, dans l'implémentation A
. Il est fait comme:
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);
}
}
puisque A.recursive()
invoque doRecursive()
et doRecursive()
ne peut jamais être dépassé, A
est assuré qu'il appelle sa propre logique.
super.recursive(i + 1);
dans la classe B
les appels de la super-classe de la méthode explicite, de sorte que recursive
de A
est appelé une seule fois.
puis, recursive(i - 1);
dans la classe A appellerait la méthode recursive
dans la classe B
qui remplace recursive
de la classe A
, puisqu'elle est exécutée sur une instance de la classe B
.
Puis B
's recursive
pourrait appeler A
's recursive
explicitement, et ainsi de suite.
qui ne peut en fait pas aller d'une autre manière.
quand vous appelez B.recursive(10);
, alors il imprime B.recursive(10)
puis appelle l'implémentation de cette méthode dans A
avec i+1
.
donc vous appelez A.recursive(11)
, qui imprime A.recursive(11)
qui appelle la méthode recursive(i-1);
sur l'instance courante qui est B
avec le paramètre d'entrée i-1
, donc il appelle B.recursive(10)
, qui appelle ensuite la super implémentation avec i+1
qui est 11
, qui appelle alors récursivement l'instance actuelle récursive avec i-1
qui est 10
, et vous obtiendrez la boucle que vous voyez ici.
c'est tout parce que si vous appelez la méthode de l'instance dans la superclasse, vous appellerez toujours l'implémentation de l'instance sur laquelle vous l'appelez.
Imagine ceci,
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();
}
}
vous obtiendrez "BARK" au lieu d'un erreur de compilation telle que "la méthode abstraite ne peut pas être appelée sur cette instance" ou une erreur d'exécution AbstractMethodError
ou même pure virtual method call
ou quelque chose comme ça. Tout cela pour soutenir polymorphisme .
Lorsqu'une méthode B
de l'instance recursive
appelle l'implémentation de classe super
, l'instance sur laquelle on agit est toujours B
. Par conséquent, lorsque la mise en œuvre de la classe super appelle recursive
sans autre qualification, c'est la mise en œuvre de la sous-classe . Le résultat est la boucle sans fin vous voyez.