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 |
|---|---|---|
|
Comparación segura de dos objetos teniendo en cuenta null | |
|
Cálculo conciso del código hash a partir de varios campos | |
|
Comparación mediante comparador, segura para null | |
|
Comprobación de null, lanza NullPointerException | |
|
Comprobación de null/no null (cómodo en la API Stream) | |
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.
GO TO FULL VERSION