Cómo anular el método equals en Java

108

Estoy tratando de anular el método de igualdad en Java. Tengo una clase Peopleque básicamente tiene 2 campos de datos namey age. Ahora quiero anular el equalsmétodo para poder verificar entre 2 objetos People.

Mi código es el siguiente

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

Pero cuando escribo age.equals(other.age), me da un error, ya que el método igual solo puede comparar String y la edad es Integer.

Solución

solía == operador como se sugirió y mi problema está resuelto.

castor
fuente
3
Oye, ¿qué tal this.age == other.age? :)
denis.solonenko
1
¿Cuál es el tipo de datos para la edad? int OR Entero? Además, ¿qué versión de JDK está utilizando?
Manish
2
"como método igual solo puede comparar String" - ¿Quién te dijo que el método igual solo puede comparar String? El método equals pertenece a la clase Object y cualquier clase creada tendrá una implementación igual de forma predeterminada. Puede llamar a Equals en CUALQUIER clase de Java
Manish

Respuestas:

127
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
    public class Person {
        private String name;
        private int age;

        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            
if (obj.getClass() != this.getClass()) {
                return false;
            }



            final Person other = (Person) obj;
            if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
                return false;
            }

            if (this.age != other.age) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
            hash = 53 * hash + this.age;
            return hash;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

Salida:

correr:

- Subash Adhikari - VS - K falso

- Subash Adhikari - VS - StackOverflow falso

- Subash Adhikari - VS - Subash Adhikari verdadero

- K - VS - StackOverflow falso

- K - VS - Subash Adhikari falso

- StackOverflow - VS - Subash Adhikari falso

- CONSTRUIR CON ÉXITO (tiempo total: 0 segundos)

Kim
fuente
7
¿Por hash = 53 * hashqué lo estás usando?
kittu
2
El uso getClass()causará problemas si la clase se subclasifica y se compara con un objeto de la superclase.
Tuxdude
1
puede ser bcoz 53 es el número primo , eche un vistazo a esta respuesta stackoverflow.com/a/27609/3425489 , comentó al elegir números enhashCode()
Shantaram Tupe
1
La respuesta ganadora a esta pregunta tiene una excelente explicación de por qué anula hashCode () stackoverflow.com/a/27609/1992108
Pegasaurus
7
Considere usar if (getClass ()! = Obj.getClass ()) ... en lugar de usar el instanceofoperador o isAssignableFrom. Esto requerirá una coincidencia de tipo exacta, en lugar de una coincidencia de subtipo. - Requisito simétrico. También para comparar Stringu otros tipos de objetos, puede usar Objects.equals(this.name,other.name).
YoYo
22

La introducción de una nueva firma de método que cambia los tipos de parámetros se denomina sobrecarga :

public boolean equals(People other){

Aquí Peoplees diferente aObject .

Cuando la firma de un método sigue siendo idéntica a la de su superclase, se denomina anulación y la @Overrideanotación ayuda a distinguir los dos en tiempo de compilación:

@Override
public boolean equals(Object other){

Sin ver la declaración real de age, es difícil decir por qué aparece el error.

fortran
fuente
18

No estoy seguro de los detalles ya que no ha publicado el código completo, pero:

  • recuerda anular hashCode() también
  • el equalsmétodo debería tener Object, noPeople como su tipo de argumento. En este momento, está sobrecargando, no anulando, el método equals, que probablemente no sea lo que desea, especialmente dado que verifica su tipo más tarde.
  • puede utilizar instanceofpara comprobar si es un objeto de personas, por ejemploif (!(other instanceof People)) { result = false;}
  • equalsse utiliza para todos los objetos, pero no para los primitivos. Creo que quieres decir que la edad es un int(primitivo), en cuyo caso solo usa ==. Tenga en cuenta que un entero (con una 'I' mayúscula) es un objeto que debe compararse con iguales.

Consulte ¿Qué problemas deben tenerse en cuenta al anular equals y hashCode en Java? para más detalles.

Adrian Mouat
fuente
12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}
NeverJr
fuente
12

Ítem ​​10: Obedezca el contrato general cuando prevalezca igual

De acuerdo con Effective Java , anular el equalsmétodo parece simple, pero hay muchas formas de hacerlo mal y las consecuencias pueden ser nefastas. La forma más fácil de evitar problemas es no anular el equalsmétodo, en cuyo caso cada instancia de la clase es igual a sí misma. Esto es lo correcto si se aplica alguna de las siguientes condiciones:

  • Cada instancia de la clase es inherentemente única . Esto es cierto para clases como Thread que representan entidades activas en lugar de valores. La implementación igual proporcionada por Object tiene exactamente el comportamiento correcto para estas clases.

  • No es necesario que la clase proporcione una prueba de "igualdad lógica". Por ejemplo, java.util.regex.Pattern podría haber anulado los iguales para verificar si dos instancias de Pattern representaban exactamente la misma expresión regular, pero los diseñadores no pensaron que los clientes necesitarían o querrían esta funcionalidad. En estas circunstancias, la implementación de iguales heredada de Object es ideal.

  • Una superclase ya ha anulado los iguales y el comportamiento de la superclase es apropiado para esta clase. Por ejemplo, la mayoría de las implementaciones de Set heredan su implementación igual de AbstractSet, las implementaciones de List de AbstractList y las implementaciones de Map de AbstractMap.

  • La clase es privada o privada de paquete , y usted está seguro de que su método equals nunca será invocado. Si es extremadamente reacio al riesgo, puede anular el método equals para asegurarse de que no se invoque accidentalmente:

El equalsmétodo implementa una relación de equivalencia. Tiene estas propiedades:

  • Reflexivo: para cualquier valor de referencia no nulo x, x.equals(x)debe devolver verdadero.

  • Simétrico: para cualquier valor de referencia no nulo xy y, x.equals(y)debe devolver verdadero si y solo si y.equals (x) devuelve verdadero.

  • Transitiva: Para cualquier valor de referencia que no son nulos x, y, z, si x.equals(y)vuelve truey y.equals(z)vuelve true, entonces x.equals(z)debe devolver true.

  • Consistente: para cualquier valor de referencia no nulo xy y, múltiples invocaciones de x.equals(y)deben regresar trueo regresar consistentemente false, siempre que no se modifique la información utilizada en comparaciones iguales.

  • Para cualquier valor de referencia no nulo x, x.equals(null)debe regresar false.

Aquí hay una receta para un método igual de alta calidad:

  1. Utilice el ==operador para comprobar si el argumento es una referencia a este objeto. Si es así, devuelve verdadero. Esta es solo una optimización del rendimiento, pero vale la pena hacerla si la comparación es potencialmente costosa.

  2. Utilice el instanceofoperador para comprobar si el argumento es del tipo correcto. Si no, devuelve falso. Normalmente, el tipo correcto es la clase en la que se produce el método. Ocasionalmente, es alguna interfaz implementada por esta clase. Utilice una interfaz si la clase implementa una interfaz que refina el contrato de iguales para permitir comparaciones entre las clases que implementan la interfaz. Las interfaces de colección como Set, List, Map y Map.Entry tienen esta propiedad.

  3. Transmita el argumento al tipo correcto. Debido a que este lanzamiento fue precedido por una instancia de prueba, se garantiza que tendrá éxito.

  4. Para cada campo "significativo" de la clase, compruebe si ese campo del argumento coincide con el campo correspondiente de este objeto. Si todas estas pruebas tienen éxito, devuelve verdadero; de lo contrario, devuelve falso. Si el tipo en el Paso 2 es una interfaz, debe acceder a los campos del argumento a través de métodos de interfaz; si el tipo es una clase, es posible que pueda acceder a los campos directamente, según su accesibilidad.

  5. Para campos primitivos cuyo tipo no es floato double, use el ==operador para comparaciones; para campos de referencia de objeto, llame al equalsmétodo de forma recursiva; para los floatcampos, use el Float.compare(float, float)método estático ; y para doublecampos, use Double.compare(double, double). El tratamiento especial de los campos flotantes y dobles se hace necesario por la existencia de Float.NaN, -0.0fy los valores dobles análogos; Si bien podría comparar los campos floaty doublecon los métodos estáticos Float.equalsy Double.equals, esto implicaría un ajuste automático en cada comparación, lo que tendría un rendimiento deficiente. Para los arraycampos, aplique estas pautas a cada elemento. Si todos los elementos de un campo de matriz son significativos, utilice uno de los Arrays.equalsmétodos.

  6. Algunos campos de referencia de objetos pueden contener legítimamente null. Para evitar la posibilidad de a NullPointerException, verifique la igualdad de dichos campos utilizando el método estático Objects.equals(Object, Object).

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }

fuente
1
No olvide mencionar que también debe anular hashCode(). Nota también, que desde la escritura Java7 equals()y hashCode()métodos se ha vuelto mucho más fácil mediante el uso Objects.equals(), Arrays.equals()y Objects.hashCode(), Arrays.hashCode().
Arnold Schrijver
3
Considere usar en if (getClass() != obj.getClass()) ...lugar de usar el operador instanceof. Esto requerirá una coincidencia de tipo exacta , en lugar de una coincidencia de subtipo. - Requisito simétrico.
YoYo
@YoYo es correcto ... usar instanceof podría fallar en la propiedad simétrica. Si o es una subclase de PhoneNumber como tal vez PhoneNumberWithExtension, y anula la misma forma usando instanceof, entonces o.equals (esto) fallaría la instancia de prueba mientras que PhoneNumber.equals lo pasaría y devolvería verdadero (asumiendo todos los demás campos de PhoneNumber son iguales).
ldkronos
5

Como supongo que agees de tipo int:

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals
Luchian Grigore
fuente
Esto resultará en un NullPointerExceptionif nameis null.
orien,
@orien No es gran cosa, tal vez esté en el contrato que namenunca se le asigna un nullvalor ...
fortran
@fortran Entonces ... tal vez no sea gran cosa;)
orien
5

Al comparar objetos en Java, realiza una verificación semántica , comparando el tipo y el estado de identificación de los objetos con:

  • sí mismo (misma instancia)
  • sí mismo (clon o copia reconstruida)
  • otros objetos de diferentes tipos
  • otros objetos del mismo tipo
  • null

Reglas:

  • Simetría :a.equals(b) == b.equals(a)
  • equals()siempre cede trueo false, pero nunca un NullpointerException, ClassCastExceptiono cualquier otro arrojable

Comparación:

  • Verificación de tipo : ambas instancias deben ser del mismo tipo, lo que significa que debe comparar las clases reales para determinar la igualdad. Esto a menudo no se implementa correctamente, cuando los desarrolladores lo utilizan instanceofpara la comparación de tipos (que solo funciona mientras no haya subclases y viola la regla de simetría cuandoA extends B -> a instanceof b != b instanceof a) .
  • Comprobación semántica del estado de identificación : asegúrese de comprender en qué estado se identifican las instancias. Las personas pueden identificarse por su número de seguro social, pero no por el color de cabello (se puede teñir), el nombre (se puede cambiar) o la edad (cambia todo el tiempo). Solo con objetos de valor debe comparar el estado completo (todos los campos no transitorios); de lo contrario, verifique solo lo que identifica la instancia.

Para tu Personclase:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

Clase de utilidad genérica reutilizable:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

Para su Personclase, usando esta clase de utilidad:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}
Peter Walser
fuente
1

si age es int, debe usar == si es un objeto Integer, entonces puede usar equals (). También debe implementar el método hashcode si anula los iguales. Los detalles del contrato están disponibles en el javadoc de Object y también en varias páginas de la web.

Ashwinee K Jha
fuente
0

Aquí está la solución que utilicé recientemente:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

}
SSharma
fuente