La ejecución de un programa Java requiere dos pasos: compilación y ejecución (interpretación). Al mismo tiempo, Java es un lenguaje de programación orientado a objetos. Cuando la subclase y la clase principal tienen el mismo método y la subclase anula el método de la clase principal, cuando el programa llama al método en tiempo de ejecución, ¿debe llamar al método de la clase principal o al método anulado de la subclase? Debería ser la pregunta que encontramos cuando aprendemos Java por primera vez. Aquí primero determinaremos qué método llamar o la operación de variables se llama vinculación.
Hay dos métodos de enlace en Java, uno es el enlace estático, también llamado enlace temprano. El otro es el enlace dinámico, también conocido como enlace tardío.
Comparación de diferencias
1. El enlace estático se produce en tiempo de compilación y el enlace dinámico se produce en tiempo de ejecución.
2. Utilice variables o métodos modificados con privado, estático o final, y utilice enlace estático. Los métodos virtuales (métodos que pueden ser anulados por subclases) se vincularán dinámicamente en función del objeto de tiempo de ejecución.
3. El enlace estático se completa utilizando información de clase, mientras que el enlace dinámico debe completarse utilizando información de objeto.
4. El método sobrecargado se completa mediante enlace estático, mientras que el método anulado se completa mediante enlace dinámico.
Ejemplo de método sobrecargado
A continuación se muestra un ejemplo de métodos sobrecargados.
Copie el código de código de la siguiente manera:
prueba de clase pública principal {
público estático vacío principal (String [] argumentos) {
Cadena cadena = nueva cadena();
Persona que llama persona que llama = nueva persona que llama();
llamador.call(str);
}
Llamador de clase estática {
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en el llamador");
}
llamada pública nula (String str) {
System.out.println("una instancia de cadena en el llamador");
}
}
}
El resultado de la ejecución es
Copie el código de código de la siguiente manera:
22:19 $javaTestPrincipal
una instancia de cadena en Caller
En el código anterior, hay dos implementaciones sobrecargadas del método de llamada. Una recibe un objeto de tipo Objeto como parámetro y la otra recibe un objeto de tipo Cadena como parámetro. str es un objeto String y se llamarán todos los métodos de llamada que reciban parámetros de tipo String. El enlace aquí es un enlace estático basado en el tipo de parámetro en el momento de la compilación.
verificar
Solo mirar la apariencia no puede probar que se realice un enlace estático. Puede verificarlo usando javap para compilarlo.
Copie el código de código de la siguiente manera:
22:19 $ javap -c PruebaMain
Compilado de "TestMain.java"
prueba de clase pública principal {
prueba pública principal();
Código:
0: carga_0
1: invocar especial #1 // Método java/lang/Object."<init>":()V
4: regreso
principal vacío estático público (java.lang.String []);
Código:
0: nuevo #2 // clase java/lang/String
3: doble
4: invocar especial #3 // Método java/lang/String."<init>":()V
7: astore_1
8: nuevo #4 // clase TestMain$Caller
11: doble
12: invocar especial #5 // Método TestMain$Caller."<init>":()V
15: astore_2
16: carga_2
17: carga_1
18: invocarvirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: regreso
}
Vi esta línea 18: invokevirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V de hecho está vinculado estáticamente, lo que confirma que se llama al método de llamada que recibe un objeto String como parámetro.
Ejemplo de anulación de un método
Copie el código de código de la siguiente manera:
prueba de clase pública principal {
público estático vacío principal (String [] argumentos) {
Cadena cadena = nueva cadena();
Persona que llama = nueva SubCaller();
llamador.call(str);
}
Llamador de clase estática {
llamada pública nula (String str) {
System.out.println("una instancia de String en Caller");
}
}
clase estática SubCaller extiende Caller {
@Anular
llamada pública nula (String str) {
System.out.println("una instancia de String en SubCaller");
}
}
}
El resultado de la ejecución es
Copie el código de código de la siguiente manera:
22:27 $javaTestPrincipal
una instancia de cadena en SubCaller
En el código anterior, hay una implementación del método de llamada en Caller. SubCaller hereda a Caller y reescribe la implementación del método de llamada. Declaramos una variable callerSub de tipo Caller, pero esta variable apunta a un objeto SubCaller. Según los resultados, se puede ver que llama a la implementación del método de llamada de SubCaller en lugar del método de llamada de Caller. La razón de este resultado es que el enlace dinámico ocurre en tiempo de ejecución y durante el proceso de enlace es necesario determinar qué versión de la implementación del método de llamada llamar.
verificar
El enlace dinámico no se puede verificar directamente usando javap, y si se demuestra que no se realiza el enlace estático, significa que se realiza el enlace dinámico.
Copie el código de código de la siguiente manera:
22:27 $ javap -c PruebaMain
Compilado de "TestMain.java"
prueba de clase pública principal {
prueba pública principal();
Código:
0: carga_0
1: invocar especial #1 // Método java/lang/Object."<init>":()V
4: regreso
principal vacío estático público (java.lang.String []);
Código:
0: nuevo #2 // clase java/lang/String
3: doble
4: invocar especial #3 // Método java/lang/String."<init>":()V
7: astore_1
8: nuevo #4 // clase TestMain$SubCaller
11: doble
12: invocar especial #5 // Método TestMain$SubCaller."<init>":()V
15: astore_2
16: carga_2
17: carga_1
18: invocarvirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: regreso
}
Como resultado anterior, 18: invokevirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V Este es TestMain$Caller.call en lugar de TestMain$SubCaller.call, porque no se puede determinar la subrutina de llamada en tiempo de compilación La clase sigue siendo la implementación de la clase principal, por lo que solo puede manejarse mediante enlace dinámico en tiempo de ejecución.
Cuando recargar se encuentra con reescribir
El siguiente ejemplo es un poco anormal. Hay dos sobrecargas del método de llamada en la clase Caller. Lo que es más complicado es que SubCaller integra Caller y anula estos dos métodos. De hecho, esta situación es una situación compuesta de las dos situaciones anteriores.
El siguiente código primero realizará un enlace estático para determinar el método de llamada cuyo parámetro es un objeto String, y luego realizará un enlace dinámico en tiempo de ejecución para determinar si se ejecuta la implementación de llamada de la subclase o de la clase principal.
Copie el código de código de la siguiente manera:
prueba de clase pública principal {
público estático vacío principal (String [] argumentos) {
Cadena cadena = nueva cadena();
Llamante callerSub = new SubCaller();
callerSub.call(str);
}
Llamador de clase estática {
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en el llamador");
}
llamada pública nula (String str) {
System.out.println("una instancia de cadena en el llamador");
}
}
clase estática SubCaller extiende Caller {
@Anular
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en SubCaller");
}
@Anular
llamada pública nula (String str) {
System.out.println("una instancia de String en SubCaller");
}
}
}
El resultado de la ejecución es
Copie el código de código de la siguiente manera:
22:30 $javaTestPrincipal
una instancia de cadena en SubCaller
verificar
Como se presentó anteriormente, solo publicaré aquí los resultados de la descompilación.
Copie el código de código de la siguiente manera:
22:30 $ javap -c PruebaMain
Compilado de "TestMain.java"
prueba de clase pública principal {
prueba pública principal();
Código:
0: carga_0
1: invocar especial #1 // Método java/lang/Object."<init>":()V
4: regreso
principal vacío estático público (java.lang.String []);
Código:
0: nuevo #2 // clase java/lang/String
3: doble
4: invocar especial #3 // Método java/lang/String."<init>":()V
7: astore_1
8: nuevo #4 // clase TestMain$SubCaller
11: doble
12: invocar especial #5 // Método TestMain$SubCaller."<init>":()V
15: astore_2
16: carga_2
17: carga_1
18: invocarvirtual #6 // Método TestMain$Caller.call:(Ljava/lang/String;)V
21: regreso
}
Preguntas curiosas
¿No es posible utilizar enlace dinámico?
De hecho, en teoría, la vinculación de ciertos métodos también se puede lograr mediante vinculación estática. Por ejemplo:
Copie el código de código de la siguiente manera:
público estático vacío principal (String [] argumentos) {
Cadena cadena = nueva cadena();
final Caller callerSub = new SubCaller();
llamadorSub.call(str);
}
Por ejemplo, aquí callerSub contiene el objeto de subCaller y la variable callerSub es final, y el método de llamada se ejecuta inmediatamente. En teoría, el compilador puede saber que el método de llamada de SubCaller debe llamarse mediante un análisis suficiente del código.
Pero, ¿por qué no existe un enlace estático?
Supongamos que nuestro Caller hereda de la clase BaseCaller de un marco determinado, que implementa el método de llamada, y BaseCaller hereda de SuperCaller. El método de llamada también se implementa en SuperCaller.
Supongamos que BaseCaller y SuperCaller en un determinado marco 1.0
Copie el código de código de la siguiente manera:
clase estática SuperCaller {
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en SuperCaller");
}
}
clase estática BaseCaller extiende SuperCaller {
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en BaseCaller");
}
}
Implementamos esto usando el marco 1.0. La persona que llama hereda de BaseCaller y llama al método super.call.
Copie el código de código de la siguiente manera:
prueba de clase pública principal {
público estático vacío principal (String [] argumentos) {
Objeto obj = nuevo Objeto();
SuperCaller callerSub = nuevo SubCaller();
llamadorSub.call(obj);
}
La clase estática que llama extiende BaseCaller {
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en el llamador");
super.llamada(obj);
}
llamada pública nula (String str) {
System.out.println("una instancia de cadena en el llamador");
}
}
clase estática SubCaller extiende Caller {
@Anular
llamada pública nula (Objeto obj) {
System.out.println("una instancia de objeto en SubCaller");
}
@Anular
llamada pública nula (String str) {
System.out.println("una instancia de String en SubCaller");
}
}
}
Luego compilamos el archivo de clase basado en la versión 1.0 de este marco. Suponiendo que el enlace estático puede determinar que la super.call del llamador anterior se implementa como BaseCaller.call.
Luego asumimos nuevamente que BaseCaller no reescribe el método de llamada de SuperCaller en la versión 1.1 de este marco. Luego, la suposición anterior de que la implementación de llamadas que se puede vincular estáticamente causará problemas en la versión 1.1, porque super.call debería usar SuperCall en la versión. 1.1. Implementación del método de llamada, en lugar de asumir que la implementación del método de llamada de BaseCaller está determinada por un enlace estático.
Por lo tanto, algunas cosas que en realidad se pueden vincular estáticamente simplemente se vinculan dinámicamente por razones de seguridad y coherencia.
¿Se obtuvo inspiración de optimización?
Dado que el enlace dinámico necesita determinar qué versión de la implementación del método o la variable ejecutar en tiempo de ejecución, requiere más tiempo que el enlace estático.
Por lo tanto, sin afectar el diseño general, podemos considerar modificar métodos o variables con privados, estáticos o finales.