web-dev-qa-db-fra.com

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

}

Permet maintenant d'appeler 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, un compte à rebours 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 dans la 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 imaginé, mais cela me fait me demander.

Question plus ancienne avec un cas d'utilisation concret .

84
raupach

C'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.

75
Tunaki

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 récursif 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
CoderCroc

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'est pas possible de la récupérer, sauf via super. B.recursive() invoque A.recursive(). A.recursive() appelle ensuite recursive(), qui se résout par le remplacement dans B. Et nous ping-pong d'avant en arrière jusqu'à la fin de l'univers ou un 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 les choses et aurait d'autres conséquences malheureuses, donc this.recursive(i-1) dans A invoque B.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'une super.recursive() dans un sous-type de A soit piégée, pour ainsi dire, dans l'implémentation A. 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() invoque doRecursive() et doRecursive() ne peut jamais être surchargé, A est assuré qu'il appelle sa propre logique.

27
Erick G. Hagstrom

Cela ne peut en réalité pas aller autrement.

Lorsque vous appelez B.recursive(10);, il imprime B.recursive(10) puis appelle l'implémentation de cette méthode dans A avec i+1.

Vous appelez donc A.recursive(11), qui imprime A.recursive(11) qui appelle la méthode recursive(i-1); sur l'instance actuelle 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 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(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ême pure virtual method call Ou quelque chose comme ça. Donc, c'est tout pour soutenir polymorphisme .

16
EpicPandaForce

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 appelle la méthode recursive dans la classe B qui remplace recursive de la classe A, car elle est exécuté sur une instance de classe B.

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

16
Eran

Lorsqu'une méthode B d'une instance recursive appelle l'implémentation superclass, l'instance faisant l'objet de l'action est toujours de 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.

14
jonrsharpe