CodeGym /Cursos /JAVA 25 SELF /Clase Objects: métodos equals, hashCode, hash

Clase Objects: métodos equals, hashCode, hash

JAVA 25 SELF
Nivel 29 , Lección 1
Disponible

1. Introducción a la clase Objects

¡Vamos al tema! Si estás cansado de comprobar manualmente los valores contra null y de escribir el cálculo del hash para cada campo, te ayudará la clase utilitaria java.util.Objects. Su objetivo — hacer el trabajo con objetos más simple, conciso y seguro.

Es realmente un «cuchillo suizo»: la clase sabe comparar objetos por igualdad de forma segura (sin riesgo de obtener NullPointerException), calcular cómodamente códigos hash, comparar mediante un comparador y comprobar argumentos contra null.

Objects.equals: comparación segura teniendo en cuenta null

Si escribes simplemente a.equals(b) y a resulta ser null, te llevarás un NullPointerException. Las comprobaciones manuales son voluminosas. Objects.equals(a, b) hace todo por ti:

  • Si ambos son null — devuelve true.
  • Si exactamente uno es null — devuelve false.
  • Si ambos no son null — invoca el equals normal.
import java.util.Objects;

String a = null;
String b = "Java";
System.out.println(Objects.equals(a, b)); // false

String c = null;
System.out.println(Objects.equals(a, c)); // true

String d = "Java";
String e = "Java";
System.out.println(Objects.equals(d, e)); // true

¿Por qué es útil? El código es más corto, más limpio y está protegido contra NPE accidentales.

2. Objects.hash y hashCode: cálculo de hash conciso

Al sobrescribir hashCode junto con equals, es fácil equivocarse, especialmente cuando hay muchos campos. El código manual a menudo resulta voluminoso y frágil:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    return result;
}

El método Objects.hash resuelve el problema — breve, seguro y con soporte de null:

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    // ... constructor, getters, etc.

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Punto importante: Objects.hash usa varargs y crea un array — en lugares muy críticos de alto rendimiento, un hashCode manual puede ser más rápido. Para la mayoría de las aplicaciones, la diferencia es insignificante.

3. Objects.compare: delegar la comparación a un comparador

A veces es necesario comparar dos objetos mediante un Comparator preparado de antemano. En lugar de llamar directamente a comparator.compare(a, b), puedes usar:

int result = Objects.compare(a, b, comparator);

Este método:

  • Devuelve 0 si los objetos son iguales.
  • Considera null «menor» que cualquier objeto no null.
  • En los demás casos delega la lógica al comparador proporcionado.
import java.util.Comparator;
import java.util.Objects;

class Person {
    private String name;
    Person(String name) { 
        this.name = name; 
    }
    public String getName() { 
        return name; 
    }
}

public class Main {
    public static void main(String[] args) {
        Person a = new Person("Anna");
        Person b = new Person("Boris");
        Comparator<Person> byName = Comparator.comparing(Person::getName);

        System.out.println(Objects.compare(a, b, byName)); // <0, porque "Anna" < "Boris"
        System.out.println(Objects.compare(a, null, byName)); // >0, porque a != null
        System.out.println(Objects.compare(null, b, byName)); // <0, porque null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull: seguro contra errores «invisibles»

Si un método debe aceptar solo valores no-null, compruébalo de inmediato. Objects.requireNonNull lanzará NullPointerException con tu mensaje:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "El nombre no puede ser null");
}

5. Ejemplo: implementación correcta de equals, hashCode y compareTo con Objects

import java.util.Objects;

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = Objects.requireNonNull(name, "El nombre no puede ser null");
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // Comparación por referencia
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // Comparación segura con null
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // Conciso y seguro
    }

    @Override
    public int compareTo(Person other) {
        // Primero comparamos por nombre y luego por edad
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

Ahora puedes almacenar objetos en HashSet, usarlos como claves en HashMap, comparar por igualdad y ordenar listas (por ejemplo, mediante Collections.sort).

6. Aplicación en tareas reales: reducción de código y menos errores

Ejemplo: lista de usuarios en una aplicación

Gracias a la pareja correcta equals/hashCode, la búsqueda en colecciones funciona de forma predecible:

import java.util.ArrayList;
import java.util.List;

List<Person> users = new ArrayList<>();
users.add(new Person("Anna", 25));
users.add(new Person("Boris", 30));

Person search = new Person("Anna", 25);
System.out.println(users.contains(search)); // true

Ejemplo: trabajo con campos nullable

Si una clase tiene campos que pueden ser null (por ejemplo, segundo nombre), usa Objects.equals y Objects.hash:

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // Puede ser null
    private String lastName;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(firstName, user.firstName)
            && Objects.equals(middleName, user.middleName)
            && Objects.equals(lastName, user.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, middleName, lastName);
    }
}

7. Tabla: métodos principales de la clase Objects

Método Propósito Ejemplo de uso
Objects.equals(a, b)
Comparación segura de dos objetos teniendo en cuenta null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Cálculo conciso del código hash a partir de varios campos
Objects.hash(name, age)
Objects.compare(a, b, comparator)
Comparación mediante comparador, segura para null
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Comprobación de null, lanza NullPointerException
Objects.requireNonNull(name, "El nombre no puede ser null")
Objects.isNull(obj) / Objects.nonNull(obj)
Comprobación de null/no null (cómodo en la API Stream)
list.stream().filter(Objects::nonNull)

8. Errores típicos al usar los métodos de la clase Objects

Error n.º 1: olvidaste usar Objects.equals para campos nullable. Si comparas los campos directamente con equals, puedes provocar un NullPointerException. Usa Objects.equals(middleName, other.middleName).

Error n.º 2: no se tienen en cuenta todos los campos en hashCode. Los campos que participan en equals deben participar también en hashCode; de lo contrario, el comportamiento de HashSet/HashMap será impredecible.

Error n.º 3: hashCode manual con error. No es trivial respetar los coeficientes y las comprobaciones de null. Objects.hash lo hace por ti; úsalo salvo que haya requisitos estrictos de rendimiento.

Error n.º 4: no usas Objects.requireNonNull donde forma parte del contrato de la clase. Si un campo no admite null, compruébalo en el constructor/setter — el error se manifestará de inmediato y no «en lo más profundo» de la pila de llamadas.

Error n.º 5: usas Objects.hash para arrays. Para arrays usa Arrays.hashCode, y para arrays anidados — Arrays.deepHashCode; de forma análoga, para comparar contenido existen Arrays.equals/Arrays.deepEquals.

1
Tarea
JAVA 25 SELF, nivel 29, lección 1
Bloqueada
Creación de una huella digital para los libros en el catálogo 📚
Creación de una huella digital para los libros en el catálogo 📚
1
Tarea
JAVA 25 SELF, nivel 29, lección 1
Bloqueada
Ordenar participantes del evento por nombre 🧑‍🤝‍🧑
Ordenar participantes del evento por nombre 🧑‍🤝‍🧑
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION