2008-11-26 8 views
85

Ayer tuve una entrevista telefónica técnica de dos horas (que pasé, woohoo!), Pero me enmudeció por completo la siguiente pregunta sobre la vinculación dinámica en Java. Y es doblemente desconcertante porque solía enseñar este concepto a estudiantes universitarios cuando era TA hace algunos años, por lo que la perspectiva de que les di información errónea es un poco inquietante ...Java dynamic binding y overrining método

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

que afirmó que la salida debe haber sido dos declaraciones de impresión independientes desde dentro del método reemplazado equals(): en t1.equals(t3) y t3.equals(t3). El último caso es bastante obvio, y en el primer caso, aunque t1 tiene una referencia de tipo Object, se ejemplifica como tipo Test, por lo que el enlace dinámico debe 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 reemplazado: 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 Object equals()), el enlace dinámico debería llamar a para invocar la versión más específica del método en función del tipo instanciado de la referencia. ¿Qué me estoy perdiendo?

+0

encuentran amablemente mi post a esta respuesta, donde he hecho todo lo posible para explicar con más casos. Realmente agradecería sus entradas :) –

Respuesta

78

Java utiliza estática vinculante para los métodos sobrecargados, y la unión dinámica para los sobrescritos. En su ejemplo, el método equals está sobrecargado (tiene un tipo de parámetro diferente de Object.equals()), por lo que el método llamado está vinculado al tipo de referencia en tiempo de compilación.

Algunos discusión here

El hecho de que se trata de los iguales método no es realmente relevante, aparte de que es un error común a la sobrecarga en lugar de anularlo, que ya está al tanto de la base de su respuesta a la problema en la entrevista.

Editar: Una buena descripción here 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 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.

+0

Mucha gente señala que está sobrecargado y no anulado, pero incluso con eso esperarías que resolviera el sobrecargado correctamente. Hasta ahora, tu publicación es la única que responde la pregunta correctamente. –

+4

Mi error faltaba por completo el hecho de que el método está realmente sobrecargado en lugar de anulado. Vi "igual()" e inmediatamente pensé heredado-y-invalidado. Parece que una vez más, obtuve el concepto más amplio y más difícil, pero arruiné los detalles simples. : P – Magsol

+14

otro motivo por el que existe la anotación @Override. – Matt

4

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 el 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 Objeto de prueba mientras lo da. Llamadas de objeto objeto Object.equals (Objeto o). Buscar ese código a través de cualquier IDE debería mostrarle dos métodos equals() para Test.

+0

Esto, y la mayoría de las respuestas no tienen sentido. El problema no es el hecho de que se está utilizando la sobrecarga en lugar de anularla. Es por eso que no es el método sobrecargado usado para t1.equals (t3), cuando t1 se declara como Objeto pero se inicializa para Probar. – Robin

4

El método está sobrecargado en lugar de sobrescrito. Igual siempre toma un objeto como parámetro.

Por cierto, tiene un artículo sobre esto en la java efectiva de Bloch (que debe tener).

+0

Joshua Bloch's Effective Java? – DJClayworth

+0

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

25

El método de la Testequals no anula el método de java.lang.Objectequals. ¡Mira el tipo de parámetro! La clase Test está sobrecargando equals con un método que acepta Test.

Si se pretende anular el método equals, debe usar la anotación @Override. Esto causaría un error de compilación para señalar este error común.

+0

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

+0

+1 por ser la respuesta verdadera a los curiosos resultados de la pregunta –

+0

Por favor encuentre mi publicación en esta respuesta donde he intentado explicar mejor con casos adicionales. Realmente apreciaría tus entradas :) –

5

Java no es compatible con la covarianza en los parámetros, solo en los tipos de devolución.

En otras palabras, mientras que su tipo de devolución en un método principal puede ser un subtipo de lo que estaba en el reemplazado, eso no es cierto para los parámetros.

Si su parámetro para equals en Object es Object, poner un igual con cualquier otra cosa en una subclase será un método sobrecargado, no reemplazado. Por lo tanto, la única situación en que se llamará 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 que me entrevisten 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.

+1

Te refieres a los parámetros contravariantes. –

+0

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/algo. : P ¡Y gracias por la buena suerte, lo necesitaré! :) – Magsol

6

Curiosamente, en el código de Groovy (que podría compilarse en un archivo de clase), todas menos una de las llamadas ejecutarían la instrucció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 HACE tipados completamente dinámicos. Esto es particularmente interesante porque no tiene variables explícitamente tipadas dinámicamente. He leído en un par de lugares que esto se considera perjudicial, ya que los programadores esperan que Groovy haga lo de java.

+0

Desafortunadamente, el precio que Groovy paga es un golpe de rendimiento masivo, ya que cada invocación de método utiliza la reflexión. Esperar que un idioma funcione exactamente igual a otro generalmente se considera dañino. Uno debe ser consciente de las diferencias. –

+0

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

0

La respuesta a la pregunta "¿por qué?" así es como se define el lenguaje Java.

citar el Wikipedia article on Covariance and Contravariance:

Tipo de retorno de covarianza se implementa en el lenguaje de programación Java versión J2SE 5.0. Los tipos de parámetros tienen para ser exactamente iguales (invariantes) para la anulación de método , de lo contrario, el método se sobrecarga con una definición paralela en su lugar.

Otros idiomas son diferentes.

+0

Mi problema era más o menos 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 hay ningún concepto de anulación aquí. Es una sobrecarga de método. el método Object() de la clase Objeto toma el parámetro de referencia del tipo Objeto y este método equal() toma el parámetro de referencia del tipo Prueba.

4

Algunos señalan en Enlace dinámico (DD) y enlace estático (SB) después de buscar un rato:

1.Timing ejecutar: (Ref.1)

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

2.Used para:

  • DB: primordial
  • SB: sobrecarga (estático , privado, final) (Ref.2)

Referencia:

  1. Ejecutar resolución media método que prefieren utilizar
  2. Debido a que no se puede anular método con el modificador static, privado o definitiva
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
2

Si se añade otro método que prevalece en lugar de la sobrecarga que le explicará la llamada unión dinámica 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); 
    } 
} 
-1

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

Sin embargo, a pesar de que referencia de t1 es objeto, tiene inicialización de la clase de prueba .
Object t1 = new Test();.
Por lo tanto, en tiempo de ejecución que llama a la public boolean equals(Object other) que es un

método reemplazado

. enter image description here

Ahora, para valores de recuento como 4 y 6, es de nuevo sencillo que t3 que tiene referencia y inicialización de prueba está llamando equals() método con el parámetro como referencias objeto y es una

sobrecargado método

OK!

Una vez más, para entender mejor lo que el compilador método llamará, simplemente clic en el método y Eclipse hará hincapié en los métodos de similares tipos que se piensa que va a llamar en el momento de la compilación. Si no se llama al en tiempo de compilación, esos métodos son un ejemplo del método omitiendo.

enter image description here