Наследование и рекурсия


предположим, что у нас есть следующие классы:

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

}

теперь называем recursive в:

public class Demo {

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

}

выход, как и ожидалось, отсчет от 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)

давайте перейдем к самой запутанной части. Теперь мы зовем recursive в классе Б.

ожидается:

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)

фактический:

B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...

как это происходит? Я знаю, что это придуманный пример, но это делает меня чудо.

старый вопрос с конкретный случай использования.

6 84

6 ответов:

ожидается, что это будет. Это то, что происходит для экземпляра 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
    }

}

таким образом, вызовы чередуются между A и B.

Это не происходит в случае экземпляра A потому что переопределенный метод не будет вызван.

, потому что recursive(i - 1); на A относится к this.recursive(i - 1); что это B#recursive во втором случае. Итак,super и this будет вызван в рекурсивные функции кроме того.

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

на A

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

другие ответы все объяснили существенный момент, что как только метод экземпляра переопределен, он остается переопределенным, и нет никакого возврата, кроме как через super. B.recursive() вызывает A.recursive(). вызывает recursive(), который разрешает переопределение в B. И мы пинг-понг туда и обратно до конца Вселенной или StackOverflowError, в зависимости от того, что наступит раньше.

было бы неплохо, если бы можно было писать this.recursive(i-1) на A чтобы получить свою собственную реализацию, но это, вероятно, сломает вещи и будет иметь другие печальные последствия, так что this.recursive(i-1) на A вызывает B.recursive() и так далее.

есть способ получить ожидаемое поведение, но он требует предвидения. Другими словами, вы должны заранее знать, что вы хотите super.recursive() в подтипом A чтобы попасть в ловушку, так сказать, в 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);
    }
}

С A.recursive() вызывает doRecursive() и doRecursive() не может быть переопределен, A уверен, что он вызывает свою собственную логику.

super.recursive(i + 1); в классе B вызывает метод суперкласса явно, поэтому recursive на A вызывается один раз.

затем, recursive(i - 1); в классе А будет вызывать recursive метод в классе B который переопределяет recursive класса A, так как он выполняется на экземпляре класса B.

затем B ' s recursive назвали бы A ' s recursive явно, и так далее.

что на самом деле не может пойти другим путем.

когда вы называете B.recursive(10);, затем он печатает B.recursive(10) затем вызывает реализацию этого метода в A С i+1.

Так вы называете A.recursive(11), который печатает A.recursive(11) что называет recursive(i-1); метод на текущем экземпляре, который является B с входным параметром i-1, поэтому он называет B.recursive(10), который затем вызывает супер осуществлении с i+1 что это 11, который затем рекурсивно вызывает текущий экземпляр рекурсивный с i-1 что это 10, и вы получите петлю, которую вы видите здесь.

это все потому, что если вы вызываете метод экземпляра в суперклассе, вы все равно вызовете реализацию экземпляра, на котором вы его вызываете.

представьте себе,

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

вы получите "BARK" вместо ошибки компиляции, такой как" абстрактный метод не может быть вызван на этом экземпляре " или ошибка выполнения AbstractMethodError или даже pure virtual method call или что-то вроде того. Так что это все для поддержки полиморфизм.

Когда a B экземпляра recursive вызовы метода superреализация класса, экземпляр, на котором выполняется действие, по-прежнему имеет B. Поэтому, когда реализация суперкласса вызывает recursive без дальнейших уточнений, это реализация подкласса. Результатом является бесконечный цикл, который вы видите.