La clase Object y sus métodos
En Java, todas las clases heredan (directa o indirectamente) de java.lang.Object. Esta superclase define un conjunto de métodos básicos que cualquier objeto debe poder responder:
| Método | Propósito |
|--------|-----------|
| boolean equals(Object o) | Comparación lógica de igualdad |
| int hashCode() | Código hash para estructuras basadas en hashing |
| String toString() | Representación textual |
| Class<?> getClass() | Obtiene la clase en runtime |
| Object clone() | Crea una copia (poco usado) |
| void finalize() | Callback antes de GC (deprecated desde Java 9, removido en progresivo) |
| wait, notify, notifyAll | Primitivas de sincronización (monitor) |
Los tres críticos que debes entender y redefinir con frecuencia son equals, hashCode y toString.
toString(): representación legible
Por defecto, Object.toString() devuelve algo inútil como Persona@4e50df2e (nombre de clase y hash). Redefínelo para mostrar información significativa:
public class Persona {
private String nombre;
private int edad;
@Override
public String toString() {
return "Persona{nombre='" + nombre + "', edad=" + edad + "}";
}
}
Uso:
Persona p = new Persona("Ana", 30);
System.out.println(p); // Persona{nombre='Ana', edad=30}
System.out.println("Persona creada: " + p); // concatenación llama a toString
toString se usa en:
- Concatenación con
String. System.out.println(objeto).- Mensajes de log y depuración.
- Excepciones y mensajes de error.
Recomendación: incluye los campos relevantes sin información sensible (passwords, tokens). Mantén el formato predecible para facilitar parsing en logs.
equals(): igualdad lógica
Object.equals() por defecto compara identidad de referencia (==): dos objetos son iguales si son literalmente el mismo en memoria. Para la mayoría de clases de datos eso no es lo que quieres.
Ejemplo sin equals redefinido:
Persona a = new Persona("Ana", 30);
Persona b = new Persona("Ana", 30);
System.out.println(a.equals(b)); // false: son objetos distintos
Queremos que sean iguales si tienen el mismo estado. Hay que implementarlo:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Persona other)) return false;
return edad == other.edad && Objects.equals(nombre, other.nombre);
}
El contrato de equals
Tu implementación debe cumplir cuatro propiedades:
- Reflexiva:
x.equals(x) == true. - Simétrica:
x.equals(y) == y.equals(x). - Transitiva: si
x.equals(y)yy.equals(z)entoncesx.equals(z). - Consistente: llamadas repetidas devuelven el mismo resultado si los objetos no cambian.
Además: x.equals(null) siempre debe ser false.
Un equals que rompa el contrato provoca bugs silenciosos en HashMap, HashSet, List.contains, Collections.sort, etc.
Uso de instanceof con patrón (Java 16+)
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Persona other)) return false; // pattern matching
return edad == other.edad && Objects.equals(nombre, other.nombre);
}
El instanceof pattern matching combina el chequeo de tipo y el casting en un paso. Es más seguro y legible que:
if (!(o instanceof Persona)) return false;
Persona other = (Persona) o; // casting manual
Herencia y equals
Si usas equals en una jerarquía, usa getClass() en lugar de instanceof para evitar violar la simetría:
// Mejor en jerarquías abiertas
if (getClass() != o.getClass()) return false;
Persona other = (Persona) o;
La regla de Joshua Bloch (Effective Java): si una subclase cambia la lógica de igualdad, no puedes extender una clase instanciable con equals redefinido sin romper el contrato. En la práctica, declara las clases que redefinen equals como final o usa composición en lugar de herencia.
hashCode(): coherencia obligatoria con equals
El contrato
- Si dos objetos son iguales según
equals, deben tener el mismohashCode. - Si
hashCodees igual, los objetos no necesariamente son iguales (colisión). - El valor debe ser consistente durante la vida del objeto (mientras sus campos relevantes no cambien).
Por qué importa
HashMap, HashSet y estructuras basadas en hashing usan hashCode para localizar buckets. Si equals y hashCode no son coherentes:
// MAL: equals redefinido, hashCode no
class Mal {
int id;
public boolean equals(Object o) { /* compara por id */ }
// hashCode heredado (basado en identidad)
}
Set<Mal> set = new HashSet<>();
set.add(new Mal(1));
set.contains(new Mal(1)); // false! diferentes hashCode, no se encuentra
Implementación correcta con Objects.hash
@Override
public int hashCode() {
return Objects.hash(nombre, edad);
}
Objects.hash(Object... values) (Java 7+) combina los hashes de los campos de forma correcta. Equivalente al patrón clásico:
int result = 17;
result = 31 * result + (nombre == null ? 0 : nombre.hashCode());
result = 31 * result + edad;
return result;
El multiplicador 31 es primo y produce distribución razonable. Usa exactamente los mismos campos que consideras en equals, en el mismo orden de relevancia.
Plantilla completa para clase inmutable con equals/hashCode/toString
public final class Persona {
private final String nombre;
private final int edad;
public Persona(String nombre, int edad) {
this.nombre = Objects.requireNonNull(nombre, "nombre es obligatorio");
this.edad = edad;
}
public String nombre() { return nombre; }
public int edad() { return edad; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Persona other)) return false;
return edad == other.edad && nombre.equals(other.nombre);
}
@Override
public int hashCode() {
return Objects.hash(nombre, edad);
}
@Override
public String toString() {
return "Persona[nombre=%s, edad=%d]".formatted(nombre, edad);
}
}
java.util.Objects: utilidad imprescindible
La clase Objects provee helpers para estos contratos:
| Método | Uso |
|--------|-----|
| Objects.equals(a, b) | Compara seguro ante nulos |
| Objects.hash(a, b, c, ...) | Combina hashes de varios campos |
| Objects.hashCode(obj) | Hash seguro ante null (0 para null) |
| Objects.toString(obj, defecto) | toString con valor por defecto si null |
| Objects.requireNonNull(obj, mensaje) | Lanza NPE inmediato con mensaje |
| Objects.requireNonNullElse(obj, defecto) | Devuelve obj o defecto si null |
Objects.equals es especialmente útil porque evita escribir comprobaciones de null tediosas:
// Sin Objects.equals
return (nombre == null ? other.nombre == null : nombre.equals(other.nombre));
// Con Objects.equals
return Objects.equals(nombre, other.nombre);
Records: lo resuelven automáticamente
Los records (Java 16+) generan equals, hashCode y toString por ti siguiendo el contrato correctamente:
public record Persona(String nombre, int edad) {}
Esto equivale a una clase inmutable final con todos los boilerplate correctamente implementados. Usa records por defecto para clases de datos; solo escribe equals/hashCode/toString manualmente cuando necesites lógica especial.
Errores comunes
- Redefinir
equalssin redefinirhashCode(o al revés). - Usar solo algunos campos en
equalsy otros enhashCode(incoherencia). - Hacer
equalsdependiente de estado mutable (si el hash cambia después de meter al objeto en unHashSet, "desaparece" del set). - Comparar con
==en lugar deequalsal compararString,Integero cualquier objeto. - Olvidar la comprobación de null o tipo.
Dominar equals, hashCode y toString es fundamental para cualquier desarrollador Java profesional. Es la base de las colecciones y de un código predecible.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Java
Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Conocer los métodos heredados de Object. Implementar correctamente equals siguiendo el contrato (reflexivo, simétrico, transitivo, consistente). Implementar hashCode coherente con equals. Redefinir toString para depuración y logs. Usar Objects.equals, Objects.hash, Objects.requireNonNull. Entender por qué los records resuelven esto automáticamente.