30 de octubre de 2018

Objetos - método equals vs operador "=="

En esta entrada vamos a construir varios objetos y vamos a comprobar si son iguales o el mismo.

Lo primero es que hay que distinguir qué queremos decir con "iguales" y qué con "el mismo" (que son dos cosas distintas):
  • Ser igual (comparación): es verdadero si los aspectos que considero relevantes son el iguales. Digamos que tengo la clase Persona y esta clase tiene muchos datos personales, sin embargo, en este ejemplo, para decidir si dos personas son la misma sólo me interesa comparar el número de su tarjeta de identidad. Aunque nos parezca raro, si esa es nuestra definición (en nuestro método equals), dos objetos personas con distinto nombre, fecha de nacimiento o color de pelo serían tratadas como iguales si coinciden en ese número.
  • Ser el mismo (identidad): en la vida real es fácil: un objeto sólo es él mismo. Pensemos que en programación trabajamos el valor de un objeto con variables que apuntan a distintos o al mismo objeto. Así pues la comparación se hará entre dos variables y se dira que es verdadero si apuntan al mismo objeto utilizando el operador de igualdad "==".
En definitiva, no es lo mismo coger dos billetes iguales de la cartera que intentar coger el mismo billete dos veces.

No es lo mismo ser igual a otro (equals), que ser él mismo (==)


Como ya hemos adelantado estas preguntas (igual o el mismo) las resolvemos con dos herramientas: el operador de igualdad "==" y el método equals.

Operador de igualdad

El operador de igualdad "==" devuelve true cuando sus dos operandos apuntan al mismo objeto.

Método equals

El método equals(Object obj) debe tener una lógica implementada. Al invocarlo nos devolverá el resultado (true o false) de la comparación implementada entre nuestro objeto y el que recibe como argumento.

Si nuestro objeto fuera null la invocación fallaría. Para defendernos de este caso podemos usar una invocación estática Objects.equals(Object a, Object b).

Este método no sólo es importante para las llamadas que hagamos nosotros sino que otras clases que usemos en el futuro como colecciones o mapas entre otras, lo van a usar para su funcionamiento con lo que implementarlas correctamente es una parte fundamental para sacarlas rendimiento.



Demostración

Siguiendo con nuestro ejemplo de coches vamos a añadirle un atributo matrícula. El método equals queremos que nos devuelva true cuando la matrícula y el modelo sea el mismo, asumiendo que al coche le puede faltar una rueda o le pueden haber pintado, pero sería el mismo e igual coche. También sería un coche igual cualquier otro coche del mismo modelo al que le pusiéramos la misma matrícula (pero éste ya sería otro coche, no el mismo, sólo igual). El código completo hasta ahora quedaría así:
public class Coche {
    private String modelo;
    private String color;
    private int numeroDeRuedas;
    private String matricula;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    void setMatricula(String matricula) {
        this.matricula = matricula;
    }
    
    public Coche() {
        numeroDeRuedas = 4;
    }
    
    public Coche(String color) {
        this();
        setColor(color); // como ya tengo el setter lo utilizo
    }
    
    public Coche(String modelo, String color) {
        this(color);
        this.modelo = modelo;
    }

    @Override
    public String toString() {
        return "Placa " + matricula + " - " + modelo + " (" + getColor() + "), " + numeroDeRuedas + " ruedas";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((matricula == null) ? 0 : matricula.hashCode());
        result = prime * result + ((modelo == null) ? 0 : modelo.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Coche other = (Coche) obj;
        if (matricula == null) {
            if (other.matricula != null)
                return false;
        } else if (!matricula.equals(other.matricula))
            return false;
        if (modelo == null) {
            if (other.modelo != null)
                return false;
        } else if (!modelo.equals(other.modelo))
            return false;
        return true;
    }
    
}
Alguno puede sorprenderse al ver la implementación de los métodos equals y hashCode. Ahora no os pido que sepáis implementarlos, pero cuando se implementa uno formalmente implica que hay que implementar ambos (porque están muy unidos). Vamos a usar Eclipse para ayudarnos como de costumbre usando Source > Generate hashCode() and equals().... Simplemente elegimos en el asistente los campos que quiera tener en cuenta para comparar y le damos a Generar.

Vamos a hacer una prueba con dos coches y ver qué resultado nos dan. Este es el método main que usaremos:
Coche coche1 = new Coche("Seat Ibiza", "Rojo");
coche1.setMatricula("1234 BBB");
Coche coche2 = new Coche("Seat Ibiza", "Rojo");
coche2.setMatricula("1234 BBB");
Coche coche3 = coche1;

// Igualdad
System.out.println("coche1 y coche2 son iguales: " + coche1.equals(coche2));
System.out.println("Pinto coche2 de Negro");
coche2.setColor("Negro");
System.out.println("Los coches siguen siendo iguales: " + coche1.equals(coche2));
System.out.println("Cambio la matrícula a coche2");
coche2.setMatricula("5555 CCC");
System.out.println("Los coches siguen siendo iguales: " + coche1.equals(coche2));

//Identidad
System.out.println("coche1 y coche2 son el mismo: " + (coche1 == coche2));
System.out.println("coche1 y coche3 son el mismo: " + (coche3 == coche1));
La salida es:

coche1 y coche2 son iguales: true
Pinto coche2 de Negro
Los coches siguen siendo iguales: true
Cambio la matrícula a coche2
Los coches siguen siendo iguales: false
coche1 y coche2 son el mismo: false
coche1 y coche3 son el mismo: true

Con esto ya hemos visto que hay campos importantes para decir si dos objetos son iguales y otros que no importan. También hemos visto que dos objetos distintos pueden darnos el resultado de ser iguales si cumplen nuestra definición de igualdad.

Podría parecer que el mismo objeto siempre va a ser igual a sí mismo, pero estaríamos olvidando que con equals todo depende de nuestra implementación. Si hiciéramos un método equals (Object o) { return false; } ningún objeto que pasáramos podría evaluarse como igual. Con el operador "==" todo depende de dónde apunten los operandos.

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares