2010-05-26 16 views
24

Considere el siguiente fragmento:Mejores prácticas con respecto a iguales: ¿sobrecargar o no sobrecargar?

import java.util.*; 
public class EqualsOverload { 
    public static void main(String[] args) { 
     class Thing { 
      final int x; 
      Thing(int x)   { this.x = x; } 
      public int hashCode() { return x; } 

      public boolean equals(Thing other) { return this.x == other.x; } 
     } 
     List<Thing> myThings = Arrays.asList(new Thing(42)); 
     System.out.println(myThings.contains(new Thing(42))); // prints "false" 
    } 
} 

en cuenta que contains vuelve false !!! ¡Parece que hemos perdido nuestras cosas!

El fallo, por supuesto, es el hecho de que hemos sobrecargado accidentalmente , en lugar de anulado, Object.equals(Object). Si hubiéramos escrito class Thing de la siguiente manera, entonces contains devuelve true como se esperaba.

 class Thing { 
      final int x; 
      Thing(int x)   { this.x = x; } 
      public int hashCode() { return x; } 

      @Override public boolean equals(Object o) { 
       return (o instanceof Thing) && (this.x == ((Thing) o).x); 
      } 
     } 

Effective Java 2ª edición, artículo 36: utilizar constantemente la anotación de anulación, utiliza esencialmente el mismo argumento para recomendar que @Override se debe utilizar de forma coherente. Este consejo es bueno, por supuesto, porque si hubiésemos intentado declarar @Override equals(Thing other) en el primer fragmento, nuestro amigable pequeño compilador señalaría inmediatamente nuestro pequeño y tonto error, ya que es una sobrecarga, no una anulación.

Lo que el libro no cubre específicamente, sin embargo, es si la sobrecarga de equals es una buena idea para empezar. Esencialmente, hay 3 situaciones: Sólo

  • sobrecarga, sin anulación - incorrecto casi con seguridad!
    • Esta es esencialmente la primera fragmento anterior
  • Override solamente (sin sobrecarga) - una manera de fijar
    • Esta es esencialmente la segunda fragmento anterior
  • sobrecarga y sobrescribir combo - otra forma de arreglar

La tercera situación se ilustra con el siguiente fragmento:

 class Thing { 
      final int x; 
      Thing(int x)   { this.x = x; } 
      public int hashCode() { return x; } 

      public boolean equals(Thing other) { return this.x == other.x; } 
      @Override public boolean equals(Object o) { 
       return (o instanceof Thing) && (this.equals((Thing) o)); 
      } 
     } 

Aquí, a pesar de que ahora tenemos 2 equals método, todavía hay una lógica de igualdad, y se encuentra en la sobrecarga. El @Override simplemente delega a la sobrecarga.

Así que las preguntas son:

  • ¿Cuáles son los pros y los contras de "anular única" vs "sobrecarga & anulación combo"?
  • ¿Existe una justificación para sobrecargar equals, o es casi seguro que es una mala práctica?
+3

Diría que la mejor práctica es usar eclipse o un IDE similar para generar el método equals, haciendo algunas ediciones al código generado solo cuando sea necesario. –

+1

El enfoque creo que el mejor es la anotación (Eclipse plugin) del Proyecto Lombok @EqualsAndHashCode, que autogenera ambos equals y hashcode usando sus atributos por defecto (pero configurable). Ver http://projectlombok.org/ –

+0

@Tim Bender: El código generado por el eclipse es simplemente terrible. Al menos debes usar algo como 'Objects.equals 'de Guava para que algo sea legible. – maaartinus

Respuesta

15

No veo el caso de sobrecarga iguales, excepto que es más propenso a errores y más difícil de mantener, especialmente cuando se usa la herencia.

Aquí, puede ser extremadamente difícil mantener la reflexividad, la simetría y la transitividad o detectar sus inconsistencias, ya que siempre debe conocer el método igual real que se invoca. Solo piense en una gran jerarquía de herencia y solo algunos de los tipos implementan su propio método de sobrecarga.

Así que yo diría que simplemente no lo hagas.

+0

Estoy de acuerdo - es más trabajo sobrecargar, y le gana poco. Cosas como 'nueva cosa (0).equals ("lulz") 'todavía compila bien, y dado que el molde' Thing' → 'Object' está implícito, no hace que sea" más fácil "verificar si dos instancias' Thing' 'son iguales()' entre sí. – gustafc

+2

** Estoy totalmente ** en desacuerdo, ya que los equivalentes reemplazados serán reflexivos, transitivos y simétricos, ** si y solo si ** los sobrecargados son iguales. – aioobe

+0

@aioobe: Si 'Base' agrega una sobrecarga' igual (Base) ', y anula' equals (Object) 'para llamar a la sobrecarga, y si' Derived' anula 'equals (Object)' pero no anula 'equals (Base) ', y si' d1' y 'd2' son ambas variables de tipo' Derived' que identifican instancias que difieren solo en formas de las que 'Base' no conoce nada, entonces d1.equals ((Object) d2)' invocará el 'Derived' anula y devuelve' false', pero 'd1.equals (d2)' invocará la sobrecarga 'Base' y devolverá' true'. Como d2 es equivalente a sí mismo después del reparto, ambas comparaciones con 'd1' deberían arrojar resultados coincidentes, pero no es así. – supercat

1

Utilizo este enfoque con la anulación y el combo de sobrecarga en mis proyectos, porque el código parece un poco más limpio. No tuve problemas con este enfoque hasta ahora.

+0

tiene 2 métodos haciendo el trabajo de 1 método de 1 línea de código no es más limpio, en mi opinión –

7

Si usted tiene un solo campo como en tu ejemplo, creo que

@Override public boolean equals(Object o) { 
    return (o instanceof Thing) && (this.x == ((Thing) o).x); 
} 

es el camino a seguir. Cualquier otra cosa sería demasiado complicado imo. Pero si se agrega un campo (y no quiere pasar a la recomendación de 80 columnas por el sol) que se vería algo así como

@Override public boolean equals(Object o) { 
    if (!(o instanceof Thing)) 
     return false; 
    Thing t = (Thing) o; 
    return this.x == t.x && this.y == t.y; 
} 

que creo que es un poco más feo que

public boolean equals(Thing o) { 
    return this.x == o.x && this.y == o.y; 
} 

@Override public boolean equals(Object o) { 
    // note that you don't need this.equals(). 
    return (o instanceof Thing) && equals((Thing) o); 
} 

Así que mi La regla de oro es, básicamente, si necesita emitirla más de una vez en anular solo, haga el override-/overboys-combo.


A secundaria aspecto es la sobrecarga de tiempo de ejecución. Como se explica Java performance programming, Part 2: The cost of casting:

operaciones Downcast (también llamados conversiones de restricción en la especificación del lenguaje Java) convertir una referencia de clase ancestro a una referencia subclase. Esta operación de conversión crea una sobrecarga de ejecución, ya que Java requiere que se verifique el lanzamiento en tiempo de ejecución para asegurarse de que sea válido.

Al utilizar el sobrecargas/override-combo, el compilador, en algunos casos (no todos!) Logran prescindir de los abatidos.


Para hacer comentarios sobre el punto @Snehal, que la exposición de ambos métodos posiblemente confunde a los desarrolladores del lado del cliente: Otra opción sería permitir que los iguales sobrecargados sean privados. La elegancia se conserva, el método se puede usar internamente, mientras que la interfaz del lado del cliente se ve como se esperaba.

+1

¡Guau, esta es una respuesta bastante "todo lo que quería escuchar"! Y sí, ¡sí consideré hacer la sobrecarga 'privada' para uso interno para evitar la caída! – polygenelubricants

+0

Gracias. Gran pregunta/respuestas y discusión imo. – aioobe

+0

En realidad, no estoy de acuerdo. Al hacer que la sobrecarga sea 'privada' para uso interno, puede poco. Al hacer que la sobrecarga sea 'pública', se presentan problemas como los que describo a continuación. Tal vez 'protected' es una solución, porque luego puedes reutilizar esa parte del código en una subclase. – Marc

4

Problemas con sobrecargado Igual:

  • Todas las Colecciones proporcionadas por Java es decir; Set, List, Map usa el método reemplazado para comparar dos objetos. Entonces, incluso si sobrecarga el método igual, no resuelve el propósito de comparar dos objetos. Además, si solo sobrecarga e implementa el método de código hash, se produciría un comportamiento erróneo

  • Si ambos métodos de sobrecarga y sobrecarga son iguales y exponen ambos métodos, confundirá a los desarrolladores del lado del cliente.Es por convención que la gente cree que está anulando la clase Objeto

+2

El propio OP, excluyó la opción "solo sobrecargar". Sin embargo, su segundo punto es interesante. +1 por eso. ¿Cómo comentarías sobre hacerlo privado entonces? – aioobe

2

Hay una serie de elementos en el libro que cubren esto. (No está delante de mí, así que me referiré a los artículos como los recuerdo)

Hay un ejemplo que usa exactamente equals(..) donde se dice que no se debe usar la sobrecarga, y si se usa, se debe usar con cuidado. El elemento sobre el diseño del método advierte sobre métodos de sobrecarga con la misma cantidad de argumentos. Por lo tanto - no, no sobrecargar equals(..)

Actualización: De "Effective Java" (p.44)

Es aceptable para proporcionar tal "inflexible" es igual método, además de el normal siempre que los dos métodos devuelvan el mismo resultado, pero no hay una razón convincente para hacerlo.

Por lo tanto, no está prohibido hacerlo, pero agrega complejidad a su clase, al tiempo que no agrega ganancias.

+0

Hay un elemento sobre la sobrecarga juiciosamente, y también, si está sobrecargando, asegúrese de que se comporten de manera compatible entre ellos (es decir, no haga 'tomar (CharSequence)' y 'tomar (Cadena)' hacer cosas completamente diferentes) . La compatibilidad está ciertamente presente aquí si se aplica el patrón de reenvío (que también es una recomendación en el libro, es decir, para reenviar entre una sobrecarga 'contentEquals' a otra). Estoy muy interesado en una actualización de esta respuesta una vez que haya tenido la oportunidad de revisarla. – polygenelubricants

+0

seguro, a más tardar mañana tendré el libro en frente de mí y lo haré. (quizás eliminarlo? :)) – Bozho

+1

@polygenelubricants actualizado (finalmente :)) – Bozho

1

puedo pensar en un ejemplo muy sencillo, donde esto no funcionará correctamente y eso que nunca debe hacer esto:

class A { 
    private int x; 

    public A(int x) { 
     this.x = x; 
    } 

    public boolean equals(A other) { 
     return this.x == other.x; 
    } 

    @Override 
    public boolean equals(Object other) { 
     return (other instanceof A) && equals((A) other); 
    } 
} 

class B extends A{ 
    private int y; 

    public B(int x, int y) { 
     super(x); 
     this.y = y; 
    } 

    public boolean equals(B other) { 
     return this.equals((A)other) && this.y == other.y; 
    } 

    @Override 
    public boolean equals(Object other) { 
     return (other instanceof B) && equals((B) other); 
    } 
} 

public class Test { 
    public static void main(String[] args) { 
     A a = new B(1,1); 
     B b1 = new B(1,1); 
     B b2 = new B(1,2); 

     // This obviously returns false 
     System.out.println(b1.equals(b2)); 
     // What should this return? true! 
     System.out.println(a.equals(b2)); 
     // And this? Also true! 
     System.out.println(b2.equals(a)); 
    } 
} 

En esta prueba, se puede ver claramente que el método sobrecargado hace más daño que bien cuando se usa la herencia. En ambos casos incorrectos se llama al más genérico equals(A a), porque el compilador de Java solo sabe que a es del tipo A y que el objeto no tiene el método equals(B b) sobrecargado.

Afterthought: hacer que el sobrecargado equals privado resuelva este problema, pero, ¿de verdad lo ganó? Solo agrega un método adicional, que solo se puede invocar haciendo un molde.

+0

El problema aquí * no tiene nada que ver con la sobrecarga. * Su 'igual (Objeto)' en la clase 'A' está * roto a sí mismo *. Incluso cuando en línea y elimina las sobrecargas, todavía obtiene 'nuevos A (1) .equals (nuevo B (1, 2))' que es incorrecto, lo que rompe la transitividad. Pruebe la igualdad de 'getClass()' o [lea esto] (http://www.artima.com/lejava/articles/equality.html). – maaartinus

+0

El problema que se está demostrando existiría aunque la clase haya probado los tipos de objetos coincidentes. Si 'A 'sobrecarga' igual', entonces para el correcto funcionamiento 'B' debe anular * esa sobrecarga * de' igual', en lugar de anular 'igual (objeto)'. – supercat

Cuestiones relacionadas