Załóżmy, że mamy następujące klasy:
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);
}
}
Teraz wezwijmy recursive
klasę A:
public class Demo {
public static void main(String[] args) {
A a = new A();
a.recursive(10);
}
}
Wynik jest, zgodnie z oczekiwaniami, odliczany od 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)
Przejdźmy do zagmatwanej części. Teraz dzwonimy recursive
do klasy B.
Oczekiwano :
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)
Rzeczywiste :
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...
Jak to się stało? Wiem, że to wymyślony przykład, ale zastanawiam się.
Starsze pytanie z konkretnym przypadkiem użycia .
java
inheritance
recursion
raupach
źródło
źródło
A
jest w rzeczywistości dynamicznie wysyłane dorecursive
metody bieżącego obiektu. Jeśli pracujesz zA
obiektem, wywołanie przeniesie Cię doA.recursive()
i z rozszerzeniemB
obiektem doB.recursive()
. AleB.recursive()
zawsze dzwoniA.recursive()
. Tak więc, jeśli uruchomiszB
obiekt, przełącza się on tam iz powrotem.Odpowiedzi:
To jest oczekiwane. Tak dzieje się na przykład
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 } }
W związku z tym połączenia są wyświetlane naprzemiennie między
A
aB
.Nie dzieje się tak w przypadku wystąpienia,
A
ponieważ nadpisana metoda nie zostanie wywołana.źródło
Ponieważ
recursive(i - 1);
wA
odnosi się do tego,this.recursive(i - 1);
co jestB#recursive
w drugim przypadku. Tak,super
ithis
będzie alternatywnie wywoływana w funkcji rekurencyjnej .void recursive(int i) { System.out.println("B.recursive(" + i + ")"); super.recursive(i + 1);//Method of A will be called }
w
A
void recursive(int i) { System.out.println("A.recursive(" + i + ")"); if (i > 0) { this.recursive(i - 1);// call B#recursive } }
źródło
Wszystkie inne odpowiedzi wyjaśniły istotną kwestię, że po zastąpieniu metody instancji pozostaje ona nadpisana i nie można jej odzyskać, jak tylko przez
super
.B.recursive()
wywołujeA.recursive()
.A.recursive()
następnie wywołujerecursive()
, co powoduje nadpisanie wB
. I ping ponga tam iz powrotem, aż do końca wszechświata lubStackOverflowError
, w zależności od tego, co nastąpi wcześniej.Byłoby miło, gdyby ktoś mógł napisać
this.recursive(i-1)
wA
celu uzyskania jego własnej implementacji, ale prawdopodobnie zepsułoby to wszystko i miałoby inne niefortunne konsekwencje, takthis.recursive(i-1)
w przypadkuA
wywołańB.recursive()
i tak dalej.Istnieje sposób na uzyskanie oczekiwanego zachowania, ale wymaga to przewidywania. Innymi słowy, musisz z góry wiedzieć, że chcesz, aby
super.recursive()
a podtypem programu zostałA
, że tak powiem, uwięziony wA
implementacji. Robi się to tak: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); } }
Ponieważ
A.recursive()
wywołujedoRecursive()
idoRecursive()
nigdy nie może zostać przesłonięty,A
zapewnia się, że wywołuje własną logikę.źródło
doRecursive()
środkarecursive()
z obiektuB
działa. Jak napisał TAsk w swojej odpowiedzi, wywołanie funkcji działa podobnie,this.doRecursive()
a ObjectB
(this
) nie ma metody,doRecursive()
ponieważ jest w klasieA
zdefiniowana jakoprivate
i nie jest,protected
a zatem nie będzie dziedziczona, prawda?B
nie może zadzwonićdoRecursive()
.doRecursive()
jestprivate
, tak. Ale kiedyB
wywołujesuper.recursive()
, wywołuje implementacjęrecursive()
inA
, która ma dostęp dodoRecursive()
.super.recursive(i + 1);
in classB
jawnie wywołuje metodę superklasy, więcrecursive
ofA
jest wywoływana raz.Następnie
recursive(i - 1);
w klasie A wywołałobyrecursive
metodę w klasie,B
która przesłaniarecursive
klasęA
, ponieważ jest wykonywana na instancji klasyB
.Następnie
B
„srecursive
nazwałbymA
” srecursive
jawnie, i tak dalej.źródło
To właściwie nie może iść inaczej.
Kiedy wywołujesz
B.recursive(10);
, drukuje,B.recursive(10)
a następnie wywołuje implementację tej metody wA
withi+1
.Więc wywołujesz
A.recursive(11)
, co drukuje,A.recursive(11)
które wywołujerecursive(i-1);
metodę w bieżącej instancji, która maB
parametr wejściowyi-1
, więc wywołujeB.recursive(10)
, a następnie wywołuje super implementację, zi+1
którą jest11
, która następnie rekurencyjnie wywołuje bieżącą instancję rekurencyjną, zi-1
którą jest10
, a ty zdobądź pętlę, którą tu widzisz.To wszystko dlatego, że jeśli wywołasz metodę instancji w nadklasie, nadal będziesz wywoływał implementację instancji, w której ją wywołujesz.
Wyobraź to sobie,
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(); } }
Otrzymasz „BARK” zamiast błędu kompilacji, takiego jak „metoda abstrakcyjna nie może być wywołana w tej instancji” lub błędu w czasie wykonywania
AbstractMethodError
lub nawetpure virtual method call
czegoś podobnego. A więc to wszystko po to, by wspierać polimorfizm .źródło
Kiedy metoda
B
instancjirecursive
wywołujesuper
implementację klasy, instancja, na której działa, jest nadalB
. Dlatego, gdy wywołuje się implementację superklasyrecursive
bez dalszych kwalifikacji, jest to implementacja podklasy . Rezultatem jest niekończąca się pętla, którą widzisz.źródło