Наследование и рекурсия
предположим, что у нас есть следующие классы:
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 ответов:
ожидается, что это будет. Это то, что происходит для экземпляра
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
' srecursive
назвали быA
' srecursive
явно, и так далее.
что на самом деле не может пойти другим путем.
когда вы называете
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
без дальнейших уточнений, это реализация подкласса. Результатом является бесконечный цикл, который вы видите.