Héritage et récursivité

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

Appelons maintenant recursive en classe A:

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

La sortie est, comme prévu, 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 confuse. 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 conçu, mais je me demande.

Question plus ancienne avec un cas d’utilisation concret .

Ceci est prévu. C’est ce qui arrive 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 } } 

À ce titre, les appels alternent entre A et B

Cela ne se produit pas dans le cas d'une instance de A car la méthode de remplacement 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 second cas. Donc, super et this 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 } 

en 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, à savoir qu’une fois la méthode d’instance surchargée, elle rest surchargée et ne peut être récupérée que par le biais de super . B.recursive() appelle A.recursive() . A.recursive() appelle alors recursive() , qui résout le remplacement dans B Et nous faisons un ping-pong jusqu’à la fin de l’univers ou d’une StackOverflowError , selon la première éventualité.

Ce serait bien si on pouvait écrire this.recursive(i-1) dans A pour obtenir sa propre implémentation, mais cela casserait probablement des choses et aurait d’autres conséquences malheureuses, donc this.recursive(i-1) dans A appelle B.recursive() et ainsi de suite.

Il y a 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 souhaitez qu’un super.recursive() dans un sous-type de A soit piégé, pour ainsi dire, dans l’implémentation A C’est 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); } } 

Comme A.recursive() appelle doRecursive() et que doRecursive() ne peut jamais être remplacé, A est assuré qu’il appelle sa propre logique.

super.recursive(i + 1); dans la classe B appelle explicitement la méthode de la super classe, donc recursive de A est appelée une fois.

Ensuite, recursive(i - 1); dans la classe A, on appellerait la méthode recursive dans la classe B qui remplace la recursive de la classe A , car elle est exécutée sur une instance de classe B

Alors, le recursive B appellerait explicitement le recursive , et ainsi de suite.

Cela ne peut pas aller autrement.

Lorsque vous appelez B.recursive(10); , puis 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 A.recursive(11) qui appelle le recursive(i-1); méthode sur l’instance actuelle qui est B avec le paramètre d’entrée i-1 , elle appelle donc B.recursive(10) , qui appelle alors la super implémentation avec i+1 qui est 11 , qui appelle ensuite 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 ç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(Ssortingng[] 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ême pure virtual method call ou quelque chose du genre. Donc, tout cela pour supporter le polymorphism .

Lorsque la méthode recursive une instance B appelle l’implémentation de la super classe, l’instance sur laquelle on agit est toujours de type B Par conséquent, lorsque l’implémentation de la super classe appelle recursive sans autre qualification, c’est l’implémentation de la sous-classe . Le résultat est la boucle sans fin que vous voyez.