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
en classe A:
public class Demo {
public static void main(String[] args) {
A a = new A();
a.recursive(10);
}
}
La sortie est, comme prévu, un compte à rebours à 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 déroutante. Maintenant, nous appelons recursive
en classe B.
Attendu :
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éel :
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 peut-il arriver? Je sais que c'est un exemple inventé, mais cela me fait me demander.
Question plus ancienne avec un cas d'utilisation concret .
java
inheritance
recursion
raupach
la source
la source
A
est en fait distribué dynamiquement à larecursive
méthode de l'objet courant. Si vous travaillez avec unA
objet, l'appel vous amène àA.recursive()
, et avec unB
objet, àB.recursive()
. MaisB.recursive()
appelle toujoursA.recursive()
. Donc, si vous démarrez unB
objet, il bascule d'avant en arrière.Réponses:
Ceci 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 } }
En tant que tels, les appels alternent entre
A
etB
.Cela ne se produit pas dans le cas d'une instance de
A
car la méthode surchargée ne sera pas appelée.la source
Parce que
recursive(i - 1);
dansA
fait référence àthis.recursive(i - 1);
ce qui estB#recursive
dans le second cas. Donc,super
etthis
sera appelé en fonction récursive 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 } }
la source
Les autres réponses ont toutes expliqué le point essentiel, qu'une fois qu'une méthode d'instance est surchargée, elle reste surchargée et il n'y a pas de retour, sauf à travers
super
.B.recursive()
invoqueA.recursive()
.A.recursive()
puis invoquerecursive()
, ce qui résout le remplacement dansB
. Et nous ping-pong dans les deux sens jusqu'à la fin de l'univers ou unStackOverflowError
, selon la première éventualité.Ce serait bien si l' on pouvait écrire
this.recursive(i-1)
dansA
pour obtenir sa propre mise en œuvre, mais ce serait probablement casser des choses et avoir d' autres conséquences fâcheuses, sithis.recursive(i-1)
enA
invoqueB.recursive()
et ainsi de suite.Il existe un moyen d'obtenir le comportement attendu, mais cela nécessite de la prévoyance. En d'autres termes, vous devez savoir à l'avance que vous voulez qu'un
super.recursive()
sous-type deA
soit piégé, pour ainsi dire, dans l'A
implémentation. C'est fait comme ça: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()
invoquedoRecursive()
etdoRecursive()
ne peut jamais être remplacé, ilA
est assuré qu'il appelle sa propre logique.la source
doRecursive()
à l' intérieurrecursive()
de l'objetB
fonctionne. Comme TAsk l'a écrit dans sa réponse, un appel de fonction fonctionne commethis.doRecursive()
et ObjectB
(this
) n'a pas de méthodedoRecursive()
car il est dans la classeA
définie commeprivate
et nonprotected
et ne sera donc pas hérité, non?B
ne peut pas du tout appelerdoRecursive()
.doRecursive()
estprivate
, oui. Mais lors desB
appelssuper.recursive()
, cela invoque l'implémentation derecursive()
inA
, qui a accès àdoRecursive()
.super.recursive(i + 1);
in classB
appelle explicitement la méthode de la super classe, doncrecursive
deA
est appelée une fois.Ensuite,
recursive(i - 1);
dans la classe A appellerait larecursive
méthode de la classeB
qui remplace larecursive
classeA
, puisqu'elle est exécutée sur une instance de la classeB
.Ensuite ,
B
l 'recursive
appelleraientA
estrecursive
explicitement, et ainsi de suite.la source
Cela ne peut en fait pas aller autrement.
Lorsque vous appelez
B.recursive(10);
, il imprimeB.recursive(10)
puis appelle l'implémentation de cette méthodeA
aveci+1
.Donc, vous appelez
A.recursive(11)
, qui imprimeA.recursive(11)
qui appelle larecursive(i-1);
méthode sur l'instance actuelle qui estB
avec le paramètre d'entréei-1
, donc il appelleB.recursive(10)
, qui appelle ensuite la super implémentation aveci+1
laquelle est11
, qui appelle ensuite de manière récursive l'instance actuelle récursive aveci-1
laquelle est10
, et vous obtenez la boucle que vous voyez ici.Tout cela 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 ça,
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'une 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êmepure virtual method call
ou quelque chose comme ça. Tout cela est donc pour soutenir le polymorphisme .la source
Lorsque
B
larecursive
méthode d' une instance appelle l'super
implémentation de la classe, l'instance sur laquelle il agit est toujours deB
. Par conséquent, lorsque l'implémentation de la super classe appellerecursive
sans autre qualification, c'est l'implémentation de la sous-classe . Le résultat est la boucle sans fin que vous voyez.la source