Suponha que temos as seguintes classes:
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);
}
}
Agora vamos chamar a recursive
classe A:
public class Demo {
public static void main(String[] args) {
A a = new A();
a.recursive(10);
}
}
A saída é, conforme esperado, em contagem regressiva a partir 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)
Vamos à parte confusa. Agora chamamos a recursive
classe B.
Esperado :
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)
Real :
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
A.recursive(11)
B.recursive(10)
..infinite loop...
Como isso acontece? Eu sei que este é um exemplo inventado, mas me faz pensar.
Pergunta mais antiga com um caso de uso concreto .
java
inheritance
recursion
Raupach
fonte
fonte
A
é, na verdade, despachada dinamicamente para orecursive
método do objeto atual. Se você estiver trabalhando com umA
objeto, a chamada o levará paraA.recursive()
, e com umB
objeto, paraB.recursive()
. MasB.recursive()
sempre ligaA.recursive()
. Portanto, se você iniciar umB
objeto, ele alterna para frente e para trás.Respostas:
Isso é esperado. Isso é o que acontece para uma instância 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 } }
Como tal, as chamadas são alternadas entre
A
eB
.Isso não acontece no caso de uma instância de
A
porque o método sobrescrito não será chamado.fonte
Porque
recursive(i - 1);
emA
se refere aothis.recursive(i - 1);
que estáB#recursive
no segundo caso. Portanto,super
ethis
será chamado na função recursiva alternativamente .void recursive(int i) { System.out.println("B.recursive(" + i + ")"); super.recursive(i + 1);//Method of A will be called }
no
A
void recursive(int i) { System.out.println("A.recursive(" + i + ")"); if (i > 0) { this.recursive(i - 1);// call B#recursive } }
fonte
Todas as outras respostas explicaram o ponto essencial: uma vez que um método de instância é sobrescrito, ele permanece sobrescrito e não há como recuperá-lo, exceto por meio
super
.B.recursive()
invocaA.recursive()
.A.recursive()
em seguidarecursive()
, invoca , que resolve a substituição emB
. E jogamos pingue-pongue para frente e para trás até o fim do universo ou aStackOverflowError
, o que ocorrer primeiro.Seria bom se pudéssemos escrever
this.recursive(i-1)
emA
obter sua própria implementação, mas que provavelmente iria quebrar as coisas e ter outras consequências infelizes, entãothis.recursive(i-1)
noA
invocaB.recursive()
e assim por diante.Existe uma maneira de obter o comportamento esperado, mas requer previsão. Em outras palavras, você deve saber com antecedência que deseja que um
super.recursive()
em um subtipo deA
fique preso, por assim dizer, naA
implementação. É feito assim: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); } }
Como
A.recursive()
invocadoRecursive()
edoRecursive()
nunca pode ser substituído,A
é garantido que ele está chamando sua própria lógica.fonte
doRecursive()
dentrorecursive()
do objetoB
funciona. Como TAsk escreveu em sua resposta, uma chamada de função funciona comothis.doRecursive()
e ObjectB
(this
) não tem métododoRecursive()
porque está na classeA
definida comoprivate
e nãoprotected
e, portanto, não será herdada, certo?B
não pode fazer nenhuma chamadadoRecursive()
.doRecursive()
éprivate
, sim. Mas quandoB
chamasuper.recursive()
, isso invoca a implementação derecursive()
inA
, que tem acesso adoRecursive()
.super.recursive(i + 1);
in classB
chama o método da superclasse explicitamente, portanto,recursive
deA
é chamado uma vez.Então,
recursive(i - 1);
na classe A chamaria orecursive
método na classeB
que sobrescreve arecursive
classeA
, uma vez que é executado em uma instância da classeB
.Então,
B
'srecursive
chamariaA
' srecursive
explicitamente e assim por diante.fonte
Isso realmente não pode acontecer de outra maneira.
Quando você chama
B.recursive(10);
, ele imprimeB.recursive(10)
e chama a implementação desse métodoA
comi+1
.Então você chama
A.recursive(11)
, que imprimeA.recursive(11)
que chama orecursive(i-1);
método na instância atual que estáB
com o parâmetro de entradai-1
, então chamaB.recursive(10)
, que então chama a superimplementação comi+1
que é11
, que então chama recursivamente a instância atual recursiva comi-1
que é10
, e você pegue o loop que você vê aqui.Isso tudo porque, se você chamar o método da instância na superclasse, ainda chamará a implementação da instância na qual está chamando.
Imagina isto,
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(); } }
Você obterá "BARK" em vez de um erro de compilação, como "o método abstrato não pode ser chamado nesta instância" ou um erro de tempo de execução
AbstractMethodError
ou mesmopure virtual method call
ou algo parecido. Portanto, tudo isso é para oferecer suporte ao polimorfismo .fonte
Quando
B
orecursive
método de uma instância chama asuper
implementação da classe, a instância que está sendo atuada ainda é deB
. Portanto, quando a implementação da superclasse chamarecursive
sem qualificação adicional, essa é a implementação da subclasse . O resultado é o loop sem fim que você está vendo.fonte