Предположим, у нас есть следующие классы:
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
класс A:
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.
Ожидается :
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...
Как это произошло? Я знаю, что это надуманный пример, но это заставляет меня задуматься.
Старый вопрос с конкретным вариантом использования .
java
inheritance
recursion
Раупах
источник
источник
A
действительности динамически отправляются в поrecursive
методу текущего объекта. Если вы работаете сA
объектом, то вызов принимает васA.recursive()
, и сB
объектом, кB.recursive()
. НоB.recursive()
всегда звонитA.recursive()
. Итак, если вы запускаетеB
объект, он переключается вперед и назад.Ответы:
Это ожидаемо. Вот что происходит с экземпляром
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);
inA
относится к тому,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()
.A.recursive()
затем вызываетrecursive()
, что разрешает переопределение вB
. И мы играем в пинг-понг туда и обратно до конца вселенной или до конца светаStackOverflowError
, в зависимости от того, что наступит раньше.Было бы неплохо , если бы можно было бы написать
this.recursive(i-1)
в ,A
чтобы получить свою собственную реализацию, но это, вероятно , ломать вещи и другие неблагоприятные последствия, поэтомуthis.recursive(i-1)
вA
вызывающуюB.recursive()
и так далее.Есть способ добиться ожидаемого поведения, но он требует предвидения. Другими словами, вы должны заранее знать, что вы хотите, чтобы a
super.recursive()
in подтипа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
он уверен, что вызывает свою собственную логику.источник
doRecursive()
внутрьrecursive()
из объектаB
. Как написал TAsk в своем ответе, вызов функции работает так же, какthis.doRecursive()
и у ObjectB
(this
) нет метода,doRecursive()
потому что он находится в классе,A
определенном как,private
а не,protected
и поэтому не будет унаследован, верно?B
вообще не может позвонитьdoRecursive()
.doRecursive()
естьprivate
, да. Но приB
вызовеsuper.recursive()
это вызывает реализациюrecursive()
inA
, к которой есть доступdoRecursive()
.super.recursive(i + 1);
in classB
явно вызывает метод суперкласса, поэтомуrecursive
ofA
вызывается один раз.Затем
recursive(i - 1);
в классе A вызоветrecursive
метод в классе,B
который переопределяетrecursive
классA
, поскольку он выполняется в экземпляре классаB
.Тогда
B
«srecursive
назвали бы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
которым is11
, которая затем рекурсивно вызывает рекурсивный текущий экземпляр сi-1
которым is10
, и вы получите цикл, который вы видите здесь.Это все потому, что если вы вызовете метод экземпляра в суперклассе, вы все равно вызовете реализацию экземпляра, для которого вы его вызываете.
Представьте себе это,
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
или чего-то подобного. Итак, это все для поддержки полиморфизма .источник
Когда метод
B
экземпляраrecursive
вызываетsuper
реализацию класса, экземпляр, над которым выполняются действия, все еще находится в состоянииB
. Поэтому, когда реализация суперкласса вызываетrecursive
без дополнительной квалификации, это реализация подкласса . В результате вы видите бесконечный цикл.источник