Ayer tuve una entrevista telefónica técnica de dos horas (que pasé, ¡woohoo!), Pero borré por completo 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 asistente técnico hace unos años, por lo que la perspectiva 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 desde el equals()
método anulado : at 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í, solo había una salida del método anulado: en la línea t3.equals(t3)
.
Entonces 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 equals()
método de Object ), el enlace dinámico debería encargarse de invocar la versión más específica del método según el tipo instanciado de la referencia. ¿Qué me estoy perdiendo?
fuente
Respuestas:
Java utiliza un enlace estático para los métodos sobrecargados y un enlace dinámico para los anulados. En su ejemplo, el método equals está sobrecargado (tiene un tipo de parámetro diferente al de Object.equals ()), por lo que el método llamado está vinculado al tipo de referencia en tiempo de compilación.
Alguna discusión aquí
El hecho de que sea el método de iguales 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, pero causado por el mismo problema.
Creo que si el enlace fuera realmente dinámico, entonces cualquier caso en el que la persona que llama y el parámetro fueran una instancia de Prueba resultaría en la llamada al método anulado. Entonces t3.equals (o1) sería el único caso que no se imprimirá.
fuente
El
equals
método deTest
no anula elequals
método dejava.lang.Object
. ¡Mira el tipo de parámetro! LaTest
clase se está sobrecargandoequals
con un método que acepta unTest
.Si el
equals
método 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.fuente
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Í hace una escritura completamente dinámica. Esto es particularmente interesante porque no tiene ninguna variable que se escriba explícitamente de forma dinámica. He leído en un par de lugares que esto se considera dañino, ya que los programadores esperan que sea genial hacer lo de Java.
fuente
Java no admite la covarianza en los parámetros, solo en los tipos de devolución.
En otras palabras, si bien su tipo de retorno en un método de reemplazo puede ser un subtipo de lo que era en el reemplazo, eso no es cierto para los parámetros.
Si su parámetro para iguales en Objeto es Objeto, 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 Prueba, como en el caso de T3.
¡Buena suerte con el proceso de la entrevista de trabajo! Me encantaría ser entrevistado en una empresa que haga este tipo de preguntas en lugar de las preguntas habituales sobre estructuras de datos / algoritmos que les enseño a mis alumnos.
fuente
Creo que la clave radica en el hecho de que el método equals () no se ajusta al estándar: toma otro objeto de prueba, 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 le da llamadas de objeto Object.equals (Object o). Mirar ese código a través de cualquier IDE debería mostrarle dos métodos equals () para Test.
fuente
El método está sobrecargado en lugar de anulado. Los 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).
fuente
Algunas notas en Dynamic Binding (DD) y Static Binding̣̣̣ (SB) después de buscar un rato:
Ejecución de temporización : (Ref.1)
2.Utilizado para :
Referencia:
fuente
Si se agrega otro método que anula en lugar de sobrecargar, explicará la llamada de enlace dinámico en tiempo de ejecución.
/ * ¿Cuál es el resultado del siguiente programa? * /
fuente
Encontré un artículo interesante sobre enlace dinámico frente a enlace estático. Viene con un fragmento de código para simular el enlace dinámico. Hizo que mi código fuera más legible.
https://sites.google.com/site/jeffhartkopf/covariance
fuente
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 :
Otros idiomas son diferentes.
fuente
Está muy claro que no existe el concepto de anular aquí. Es una sobrecarga de métodos. el
Object()
método de la clase Object toma el parámetro de referencia del tipo Object y esteequal()
método toma el parámetro de referencia del tipo Test.fuente
Intentaré explicar esto a través de dos ejemplos que son las versiones extendidas de algunos de los ejemplos que encontré en línea.
Aquí, para líneas con valores de recuento 0, 1, 2 y 3; tenemos una referencia de Object para o1 y t1 en el
equals()
método. Por lo tanto, en tiempo de compilación, elequals()
método del archivo Object.class estará acotado.Sin embargo, aunque la referencia de t1 es Object , tiene la inicialización de la clase Test .
Object t1 = new Test();
.Por lo tanto, en tiempo de ejecución llama al
public boolean equals(Object other)
que es un.
Ahora, para los valores de recuento como 4 y 6, de nuevo es sencillo que t3, que tiene una referencia e inicialización de Test, llama al
equals()
método con parámetro como referencias de objeto y es un¡OKAY!
fuente