Bueno, para entender cómo la unión estática y dinámica funciona realmente el ? o ¿cómo se identifican mediante el compilador y la JVM?
Tomemos el siguiente ejemplo donde Mammal
hay una clase padre que tiene un método speak()
y la Human
clase se extiende Mammal
, anula el speak()
método y luego lo sobrecarga de nuevo 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
}
}
Cuando compilamos el código anterior e intentamos mirar el javap -verbose OverridingInternalExample
código de bytes usando , podemos ver que el compilador genera una tabla constante donde asigna códigos enteros a cada llamada de método y código de bytes para el programa que he extraído e incluido en el programa mismo ( vea los comentarios debajo de cada llamada al método)
Al observar el código de seguridad, podemos ver que el código de bytes de humanMammal.speak()
, human.speak()
y human.speak("Hindi")
son totalmente diferentes ( invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) ya que el compilador es capaz de diferenciar entre ellos sobre la base de la lista de argumentos y la referencia de clase. Debido a que todo esto se resuelve en tiempo de compilación de forma estática, es por eso que la sobrecarga de métodos se conoce como polimorfismo estático o enlace estático .
Pero el código de bytes para anyMammal.speak()
y humanMammal.speak()
es el mismo ( invokevirtual #4
) porque, según el compilador, ambos métodos se llaman por Mammal
referencia.
Entonces, ahora surge la pregunta si ambas llamadas a métodos tienen el mismo código de bytes, ¿cómo sabe JVM a qué método llamar?
Bueno, la respuesta está oculta en el propio código de bytes y es un invokevirtual
conjunto de instrucciones. JVM utiliza elinvokevirtual
instrucción para invocar el equivalente de Java de los métodos virtuales de C ++. En C ++, si queremos anular un método en otra clase, debemos declararlo como virtual, pero en Java, todos los métodos son virtuales de forma predeterminada porque podemos anular todos los métodos de la clase secundaria (excepto los métodos privados, finales y estáticos).
En Java, cada variable de referencia contiene dos punteros ocultos
- Un puntero a una tabla que nuevamente contiene métodos del objeto y un puntero al objeto Class. por ejemplo, [speak (), speak (String) Class object]
- Un puntero a la memoria asignada en el montón para los datos de ese objeto, por ejemplo, valores de variables de instancia.
Entonces, todas las referencias a objetos contienen indirectamente una referencia a una tabla que contiene todas las referencias a métodos de ese objeto. Java ha tomado prestado este concepto de C ++ y esta tabla se conoce como tabla virtual (vtable).
Una vtable es una estructura similar a una matriz que contiene nombres de métodos virtuales y sus referencias en índices de matriz. JVM crea solo una tabla virtual por clase cuando carga la clase en la memoria.
Entonces, cada vez que JVM se encuentra con un invokevirtual
conjunto de instrucciones, verifica la tabla vtable de esa clase para la referencia del método e invoca el método específico que en nuestro caso es el método de un objeto, no la referencia.
Debido a que todo esto se resuelve solo en tiempo de ejecución y en tiempo de ejecución JVM llega a saber qué método invocar, es por eso que la Anulación de método se conoce como polimorfismo dinámico o simplemente polimorfismo o enlace dinámico .
Puede leer más detalles en mi artículo ¿Cómo maneja la JVM la sobrecarga y anulación de métodos internamente ?