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 .

84
demandé sur Tunaki 2015-12-30 17:11:40

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.

74
répondu Tunaki 2015-12-30 14:14:46

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
    }
}
29
répondu coder-croc 2015-12-30 14:14:09

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.

27
répondu Erick G. Hagstrom 2015-12-31 10:57:43

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.

16
répondu Eran 2015-12-30 14:13:58

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 .

16
répondu EpicPandaForce 2015-12-30 14:24:44

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.

14
répondu jonrsharpe 2015-12-30 14:15:07