85 votes

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

Venons-en à la partie la plus 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 se fait-il ? Je sais qu'il s'agit d'un exemple inventé, mais il me fait me demander.

Question plus ancienne avec un cas d'utilisation concret .

76voto

Tunaki Points 2663

C'est 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
    }

}

Ainsi, les appels alternent entre A et B .

Cela ne se produit pas dans le cas d'une instance de A car la méthode surchargée ne sera pas appelée.

29voto

TAsk Points 7431

Parce que recursive(i - 1); sur A se réfère à this.recursive(i - 1); qui est B#recursive dans le second cas. Donc, super et this sera appelé dans récursif fonction ou encore .

void recursive(int i) {
    System.out.println("B.recursive(" + i + ")");
    super.recursive(i + 1);//Method of A will be called
}

sur A

void recursive(int i) {
    System.out.println("A.recursive(" + i + ")");
    if (i > 0) {
        this.recursive(i - 1);// call B#recursive
    }
}

27voto

Erick G. Hagstrom Points 120

Les autres réponses ont toutes expliqué le point essentiel, à savoir qu'une fois qu'une méthode d'instance est surchargée, elle reste surchargée et il n'est pas possible de la récupérer, sauf par le biais de la méthode d'instance. super . B.recursive() invoque A.recursive() . A.recursive() puis invoque recursive() qui se résout en une dérogation dans B . Et on fait du ping-pong dans les deux sens jusqu'à la fin de l'univers ou StackOverflowError selon la première éventualité.

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

Il existe un moyen d'obtenir le comportement attendu, mais il faut être prévoyant. En d'autres termes, il faut savoir à l'avance que l'on souhaite une super.recursive() dans un sous-type de A de se faire piéger, pour ainsi dire, dans la A mise en œuvre. Cela se fait de la manière suivante :

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

Depuis A.recursive() invoque doRecursive() et doRecursive() ne peuvent jamais être remplacés, A est assuré qu'il appelle sa propre logique.

16voto

Eran Points 35360

super.recursive(i + 1); en classe B appelle la méthode de la super classe de manière explicite, donc recursive de A est appelé une fois.

Ensuite, recursive(i - 1); dans la classe A appellerait le recursive dans la classe B qui prévaut sur recursive de la classe A puisqu'il est exécuté sur une instance de la classe B .

Puis B 's recursive appellerait A 's recursive explicitement, et ainsi de suite.

16voto

Zhuinden Points 3074

Il ne peut en fait en être autrement.

Lorsque vous appelez B.recursive(10); puis il imprime B.recursive(10) appelle ensuite 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 le recursive(i-1); sur l'instance courante qui est B avec un paramètre d'entrée i-1 Il appelle donc B.recursive(10) qui appelle ensuite la super implémentation avec i+1 qui est 11 qui appelle ensuite de manière récursive l'instance courante récursive avec i-1 qui est 10 et vous obtiendrez 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.

Imaginez 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'une erreur de compilation telle que "la méthode abstraite ne peut ê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. Donc tout ceci est pour soutenir polymorphisme .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X