Bem, para entender como a vinculação estática e dinâmica realmente funciona? ou como eles são identificados pelo compilador e JVM?
Vamos pegar o exemplo abaixo onde Mammal
está uma classe pai que tem um método speak()
e uma Human
classe extends Mammal
, sobrescreve o speak()
método e, em seguida, novamente o sobrecarrega com speak(String language)
.
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
// Valid overload of speak
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
// Code below contains the output and bytecode of the method calls
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
Quando compilamos o código acima e tentamos olhar para o bytecode usando javap -verbose OverridingInternalExample
, podemos ver que o compilador gera uma tabela constante onde atribui códigos inteiros para cada chamada de método e código de byte para o programa que extraí e incluí no próprio programa ( veja os comentários abaixo de cada chamada de método)
Ao olhar para o código acima, podemos ver que o bytecode de humanMammal.speak()
, human.speak()
e human.speak("Hindi")
são totalmente diferentes ( invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) porque o compilador é capaz de diferenciar entre eles com base na lista de argumentos e referência de classe. Como tudo isso é resolvido estaticamente em tempo de compilação, é por isso que a sobrecarga de método é conhecida como polimorfismo estático ou ligação estática .
Mas bytecode para anyMammal.speak()
e humanMammal.speak()
é o mesmo ( invokevirtual #4
) porque de acordo com o compilador ambos os métodos são chamados na Mammal
referência.
Portanto, agora surge a pergunta: se ambas as chamadas de método têm o mesmo bytecode, como a JVM sabe qual método chamar?
Bem, a resposta está escondida no próprio bytecode e é um invokevirtual
conjunto de instruções. O JVM usa a invokevirtual
instrução para invocar o equivalente Java dos métodos virtuais C ++. Em C ++, se quisermos sobrescrever um método em outra classe, precisamos declará-lo como virtual, mas em Java, todos os métodos são virtuais por padrão porque podemos sobrescrever todos os métodos na classe filha (exceto métodos privados, finais e estáticos).
Em Java, cada variável de referência contém dois ponteiros ocultos
- Um ponteiro para uma tabela que novamente contém métodos do objeto e um ponteiro para o objeto Class. por exemplo, [speak (), speak (String) Class objeto]
- Um ponteiro para a memória alocada no heap para os dados desse objeto, por exemplo, valores de variáveis de instância.
Portanto, todas as referências de objeto contêm indiretamente uma referência a uma tabela que contém todas as referências de método desse objeto. Java pegou emprestado esse conceito de C ++ e essa tabela é conhecida como tabela virtual (vtable).
Uma vtable é uma estrutura semelhante a um array que contém nomes de métodos virtuais e suas referências nos índices de array. A JVM cria apenas uma vtable por classe ao carregar a classe na memória.
Portanto, sempre que a JVM encontra um invokevirtual
conjunto de instruções, ela verifica a vtable dessa classe para a referência do método e invoca o método específico que, em nosso caso, é o método de um objeto, não a referência.
Como tudo isso é resolvido apenas no tempo de execução e no tempo de execução a JVM consegue saber qual método invocar, é por isso que a Substituição de Método é conhecida como Polimorfismo Dinâmico ou simplesmente Polimorfismo ou Ligação Dinâmica .
Você pode ler mais detalhes no meu artigo Como a JVM trata a sobrecarga e a substituição de métodos internamente .