Enlace dinámico de Java y anulación de métodos
-
11-07-2019 - |
Pregunta
Ayer tuve una entrevista técnica telefónica de dos horas (que aprobé, ¡woohoo!), pero acepté completamente la siguiente pregunta sobre el enlace dinámico en Java. Y es doblemente desconcertante porque solía enseñar este concepto a estudiantes universitarios cuando era un TA hace unos años, por lo que la posibilidad de que les di información errónea es un poco inquietante ...
Aquí está el problema que me dieron:
/* What is the output of the following program? */
public class Test {
public boolean equals( Test other ) {
System.out.println( "Inside of Test.equals" );
return false;
}
public static void main( String [] args ) {
Object t1 = new Test();
Object t2 = new Test();
Test t3 = new Test();
Object o1 = new Object();
int count = 0;
System.out.println( count++ );// prints 0
t1.equals( t2 ) ;
System.out.println( count++ );// prints 1
t1.equals( t3 );
System.out.println( count++ );// prints 2
t3.equals( o1 );
System.out.println( count++ );// prints 3
t3.equals(t3);
System.out.println( count++ );// prints 4
t3.equals(t2);
}
}
Afirmé que la salida debería haber sido dos declaraciones de impresión separadas dentro del método equals ()
anulado: en t1.equals (t3)
y t3 .equals (t3)
. El último caso es bastante obvio, y con el primer caso, aunque t1
tiene una referencia de tipo Object, se instancia como tipo Test, por lo que el enlace dinámico debería llamar a la forma anulada del método.
Aparentemente no. Mi entrevistador me animó a ejecutar el programa yo mismo, y he aquí que solo había una salida única del método anulado: en la línea t3.equals (t3)
.
Mi pregunta es, ¿por qué? Como ya mencioné, aunque t1
es una referencia de tipo Object (por lo que el enlace estático invocaría el método equals ()
de Object), el enlace dinámico debería tenga cuidado de invocar la versión más específica del método basada en el tipo instanciado de la referencia. ¿Qué me estoy perdiendo?
Solución
Java utiliza enlaces estáticos para métodos sobrecargados, y enlaces dinámicos para métodos anulados. En su ejemplo, el método equals está sobrecargado (tiene un tipo de parámetro diferente que Object.equals ()), por lo que el método llamado está vinculado al tipo reference en tiempo de compilación.
Alguna discusión aquí
El hecho de que sea el método de igualdad no es realmente relevante, aparte de que es un error común sobrecargarlo en lugar de anularlo, lo que ya conoce en función de su respuesta al problema en la entrevista.
Editar: Una buena descripción aquí también . Este ejemplo muestra un problema similar relacionado con el tipo de parámetro en su lugar, pero causado por el mismo problema.
Creo que si el enlace fuera realmente dinámico, cualquier caso en el que el llamador y el parámetro fueran una instancia de Prueba daría como resultado que se llamara al método anulado. Entonces t3.equals (o1) sería el único caso que no se imprimiría.
Otros consejos
El método equals
de Test
no anula el método equals
de java.lang.Object
. Mira el tipo de parámetro! La clase Test
está sobrecargando igual
con un método que acepta una Test
.
Si el método igual a
tiene la intención de anular, debe usar la anotación @Override. Esto provocaría un error de compilación para señalar este error común.
Curiosamente, en el código Groovy (que podría compilarse en un archivo de clase), todas las llamadas excepto una ejecutarían la declaración de impresión. (El que compara una Prueba con un Objeto claramente no llamará a la función Test.equals (Prueba)). Esto se debe a que groovy SÍ escribe completamente dinámico. Esto es particularmente interesante porque no tiene ninguna variable que se tipee de forma explícita y dinámica. He leído en un par de lugares que esto se considera dañino, ya que los programadores esperan que Groovy haga lo de Java.
Java no admite la covarianza en los parámetros, solo en los tipos de retorno.
En otras palabras, aunque su tipo de retorno en un método de anulación puede ser un subtipo de lo que era anulado, eso no es cierto para los parámetros.
Si su parámetro para iguales en Object es Object, poner un igual con cualquier otra cosa en una subclase será un método sobrecargado, no anulado. Por lo tanto, la única situación en la que se llamará a ese método es cuando el tipo estático del parámetro es Test, como en el caso de T3.
¡Buena suerte con el proceso de entrevista de trabajo! Me encantaría ser entrevistado en una empresa que hace este tipo de preguntas en lugar de las preguntas habituales de estructuras de datos / datos que les enseño a mis alumnos.
Creo que la clave está en el hecho de que el método equals () no se ajusta al estándar: toma otro objeto Test, no un objeto Object y, por lo tanto, no anula el método equals (). Esto significa que en realidad solo lo ha sobrecargado para hacer algo especial cuando se le da un objeto de prueba, mientras que el objeto Object llama Object.equals (Object o). Mirar ese código a través de cualquier IDE debería mostrarle dos métodos equals () para Test.
El método está sobrecargado en lugar de anulado. Iguales siempre toman un objeto como parámetro.
por cierto, tienes un elemento sobre esto en el java efectivo de Bloch (que deberías tener).
Algunas notas en Enlace dinámico (DD) y Enlace estático & # 803; & # 803; & # 803; (SB) después de buscar un momento:
1.Timing execute : (Ref.1)
- DB: en tiempo de ejecución
- SB: tiempo de compilación
2.Utilizado para :
- DB: anulación
- SB: sobrecarga (estática, privada, final) (Ref.2)
Referencia:
- Ejecutar resolución media qué método prefiere usar
- Porque no puede anular el método con un modificador estático, privado o final
- http: // javarevisited. blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
Si se agrega otro método que anule en lugar de sobrecargar, explicará la llamada de enlace dinámico en tiempo de ejecución.
/ * ¿Cuál es la salida del siguiente programa? * /
public class DynamicBinding {
public boolean equals(Test other) {
System.out.println("Inside of Test.equals");
return false;
}
@Override
public boolean equals(Object other) {
System.out.println("Inside @override: this is dynamic binding");
return false;
}
public static void main(String[] args) {
Object t1 = new Test();
Object t2 = new Test();
Test t3 = new Test();
Object o1 = new Object();
int count = 0;
System.out.println(count++);// prints 0
t1.equals(t2);
System.out.println(count++);// prints 1
t1.equals(t3);
System.out.println(count++);// prints 2
t3.equals(o1);
System.out.println(count++);// prints 3
t3.equals(t3);
System.out.println(count++);// prints 4
t3.equals(t2);
}
}
Encontré un artículo interesante sobre enlace dinámico vs.enlace estático. Viene con un código para simular el enlace dinámico. Hizo que mi código fuera más legible.
Consulte también esta pregunta SO, estrechamente relacionada: Anulación de la peculiaridad del método de igualdad de JAVA
La respuesta a la pregunta "¿por qué?" así es como se define el lenguaje Java.
Para citar el Artículo de Wikipedia sobre covarianza y contravarianza :
Se implementa la covarianza de tipo de retorno en el lenguaje de programación Java versión J2SE 5.0. Los tipos de parámetros tienen ser exactamente el mismo (invariante) para anulación del método, de lo contrario el El método está sobrecargado con un paralelo definición en su lugar.
Otros idiomas son diferentes.
Está muy claro, que no hay un concepto de anulación aquí. Es sobrecarga de métodos.
el método Object ()
de la clase Object toma el parámetro de referencia de tipo Object y este método equal ()
toma el parámetro de referencia de tipo Test.
Trataré de explicar esto a través de dos ejemplos, que son las versiones extendidas de algunos de los ejemplos que encontré en línea.
public class Test {
public boolean equals(Test other) {
System.out.println("Inside of Test.equals");
return false;
}
@Override
public boolean equals(Object other) {
System.out.println("Inside of Test.equals ot type Object");
return false;
}
public static void main(String[] args) {
Object t1 = new Test();
Object t2 = new Test();
Test t3 = new Test();
Object o1 = new Object();
int count = 0;
System.out.println(count++); // prints 0
o1.equals(t2);
System.out.println("\n" + count++); // prints 1
o1.equals(t3);
System.out.println("\n" + count++);// prints 2
t1.equals(t2);
System.out.println("\n" + count++);// prints 3
t1.equals(t3);
System.out.println("\n" + count++);// prints 4
t3.equals(o1);
System.out.println("\n" + count++);// prints 5
t3.equals(t3);
System.out.println("\n" + count++);// prints 6
t3.equals(t2);
}
}
Aquí, para líneas con valores de conteo 0, 1, 2 y 3; tenemos referencia de Objeto para o1 y t1 en el método equals ()
. Por lo tanto, en tiempo de compilación, el método equals ()
del archivo Object.class estará limitado.
Sin embargo, aunque la referencia de t1 es Objeto , tiene intialización de Clase de prueba .
Object t1 = new Test ();
.
Por lo tanto, en tiempo de ejecución llama a public boolean equals (Object other)
que es un
método anulado
Ahora, para valores de conteo como 4 y 6, nuevamente es sencillo que t3 que tiene referencia y inicialización de Test está llamando a < code> equals () método con parámetro como referencias de objeto y es un
método sobrecargado
OK!
Nuevamente, para comprender mejor a qué método llamará el compilador, solo haga clic en el método y Eclipse resaltará los métodos de similar tipos que cree que llamarán en el momento de la compilación. Si no consigue llamado en tiempo de compilación, entonces esos métodos son un ejemplo de método anulación.