Anulación de métodos y enlaces dinámicos de Java


89

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 t1tiene 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 t1es 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?


Por favor, encuentre mi publicación en esta respuesta donde he hecho todo lo posible para explicar con casos adicionales. Realmente agradecería sus aportes :)
Devendra Lattu

Respuestas:


81

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á.


Mucha gente señala que está sobrecargado y no anulado, pero incluso con eso, esperaría que resuelva el sobrecargado correctamente. Su publicación es en realidad la única hasta ahora que responde correctamente a la pregunta, por lo que puedo decir.
Bill K

4
Mi error fue omitir por completo el hecho de que el método está sobrecargado en lugar de anulado. Vi "es igual a ()" e inmediatamente pensé heredado y anulado. Parece que, una vez más, entendí bien el concepto más amplio y difícil, pero arruiné los detalles simples. : P
Magsol

14
otra razón por la que existe la anotación @Override.
Matt

1
Repita después de mí: "Java usa enlace estático para métodos sobrecargados y enlace dinámico para los anulados" - +1
Mr_and_Mrs_D

1
así que me gradué sin saber esto. ¡Gracias!
Atieh

25

El equalsmétodo de Testno anula el equalsmétodo de java.lang.Object. ¡Mira el tipo de parámetro! La Testclase se está sobrecargando equalscon un método que acepta un Test.

Si el equalsmé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.


Sí, no estoy muy seguro de por qué me perdí ese simple pero crucial detalle, pero ahí es exactamente donde estaba mi problema. ¡Gracias!
Magsol

+1 por ser la verdadera respuesta a los curiosos resultados del interrogador
matt b

Por favor, encuentre mi publicación en esta respuesta donde he hecho todo lo posible para explicar con casos adicionales. Realmente agradecería sus aportes :)
Devendra Lattu

6

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.


1
Desafortunadamente, el precio que paga Groovy es un gran impacto en el rendimiento, ya que cada invocación de método utiliza la reflexión. Esperar que un idioma funcione exactamente igual que otro generalmente se considera perjudicial. Hay que estar consciente de las diferencias.
Joachim Sauer

Debería ser agradable y rápido con invokedynamic en JDK7 (o incluso usando una técnica de implementación similar en la actualidad).
Tom Hawtin - tackline

5

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.


1
Te refieres a parámetros contravariantes.
Tom Hawtin - tackline

De alguna manera pasé por alto por completo el hecho de que los diferentes parámetros del método crean intrínsecamente un método sobrecargado, no uno anulado. Oh, no se preocupe, también hubo preguntas sobre estructuras de datos / algoritmos. : P Y gracias por la buena suerte, ¡la necesitaré! :)
Magsol

4

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.


Esto y la mayoría de las respuestas están perdiendo el sentido. El problema no se trata del hecho de que se esté usando la sobrecarga en lugar de anularla. Es por eso que no se usa el método sobrecargado para t1.equals (t3), cuando t1 se declara como Object pero se inicializa como Test.
Robin

4

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).


¿Java eficaz de Joshua Bloch?
DJClayworth

Efectivo, sí, estaba pensando en otra cosa mientras escribía: D
Gilles

4

Algunas notas en Dynamic Binding (DD) y Static Binding̣̣̣ (SB) después de buscar un rato:

Ejecución de temporización : (Ref.1)

  • DB: en tiempo de ejecución
  • SB: tiempo del compilador

2.Utilizado para :

  • DB: primordial
  • SB: sobrecarga (estática, privada, final) (Ref.2)

Referencia:

  1. Ejecute el resolutor de medias que método prefiere usar
  2. Porque no se puede anular el método con modificador estático, privado o final
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

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? * /

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);
    }
}


0

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 :

La covarianza de tipo de retorno se implementa en la versión del lenguaje de programación Java J2SE 5.0. Los tipos de parámetros tienen que ser exactamente los mismos (invariantes) para la invalidación del método; de lo contrario, el método se sobrecarga con una definición paralela.

Otros idiomas son diferentes.


Mi problema era aproximadamente equivalente a ver 3 + 3 y escribir 9, luego ver 1 + 1 y escribir 2. Entiendo cómo se define el lenguaje Java; en este caso, por la razón que sea, confundí completamente el método con algo que no era, aunque evité ese error en otra parte del mismo problema.
Magsol

0

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 este equal()método toma el parámetro de referencia del tipo Test.


-1

Intentaré 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 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, el equals()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

método anulado

. ingrese la descripción de la imagen aquí

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

método sobrecargado

¡OKAY!

Nuevamente, para comprender mejor a qué método llamará el compilador, simplemente haga clic en el método y Eclipse resaltará los métodos de tipos similares que cree que llamarán en el momento de la compilación. Si no se llama en el momento de la compilación, esos métodos son un ejemplo de anulación de métodos.

ingrese la descripción de la imagen aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.