Fundamental Java Contracts

Iterators, Sorting, and equals/hashCode Contract

1 of 7
Slide 1 / 7 • Syntax Sugar Demystified

What Really Lies Behind a for-each Loop

For-each is pure compiler syntax sugar. Under the hood, it creates an Iterator object tracking an internal cursor with hasNext() and next().

Lo que vos escribís vs Lo que compila Desazucarado
// 1. Azúcar sintáctico (Java 5+)
for (String fruta : cesta) {
    System.out.println(fruta);
}

// 2. Lo que realmente se ejecuta en Bytecode:
Iterator<String> it = cesta.iterator();
while (it.hasNext()) {
    String fruta = it.next();
    System.out.println(fruta);
}
Requisito formal: Cualquier clase puede usarse en un bucle for-each siempre que implemente la interfaz Iterable<T>, la cual exige un único método: Iterator<T> iterator().
El Cursor del Iterador en Memoria Cursor: Posición 0
[0] "Manzana"
[1] "Pera"
[2] "Uva"
[3] "Kiwi"
▲ cursor apunta aquí
it.hasNext() retorna true. Presiona it.next() para avanzar el cursor y retornar el elemento.
Slide 2 / 7 • Fail-Fast Iterators

The Nightmare of ConcurrentModificationException

Collections track mutations via modCount. The iterator holds expectedModCount. Altering the collection mid-loop causes divergence and throws an exception.

Odómetros de Mutación (modCount) Sincronizados (5 == 5)
lista.modCount 5 Mutaciones en la lista
==
it.expectedModCount 5 Copia que guarda el iterador
Estado normal: modCount coincide con expectedModCount. Todo llamado a next() es seguro.

¿Por qué falla de inmediato (Fail-Fast)?

// Implementación real de ArrayList.Itr:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

// La forma correcta de borrar mientras iterás:
Iterator<String> it = lista.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.startsWith("X")) {
        it.remove(); // Actualiza ambos contadores
    }
}
No requiere múltiples hilos: A pesar de llamarse "Concurrent", esta excepción ocurre con frecuencia en un solo hilo cuando intentás borrar de una lista dentro de un `for-each`.
Slide 3 / 7 • Intrinsic Ordering

Comparable: The Natural Order of a Class

Comparable<T> defines a single intrinsic order via compareTo(T other). The sign of the return value governs relative ordering.

Balanza de compareTo(otro): A vs B Resultado: +2 (A > B)
8 - 6 = +2 > 0
this.nota (8) - otro.nota (6) = +2. Número positivo indica que Estudiante A va DESPUÉS que B.

La Regla del Signo en compareTo

Negativo (< 0) this < otro (va antes)
Cero (== 0) this == otro (empate)
Positivo (> 0) this > otro (va después)
public class Estudiante implements Comparable<Estudiante> {
    private int nota;

    @Override
    public int compareTo(Estudiante otro) {
        return Integer.compare(this.nota, otro.nota);
    }
}
Slide 4 / 7 • Flexible Strategies

Comparator: Multiple Sorting Criteria on Demand

Sort by distinct attributes (age, name, salary) without mutating the target class, composing comparisons fluidly.

Lista de Empleados: Reordenamiento Dinámico Orden: Por Edad

Encadenamiento con Lambdas en Java 8+

// Orden por salario descendente, luego por apellido
Comparator<Empleado> comp = Comparator
    .comparing(Empleado::getSalario)
    .reversed()
    .thenComparing(Empleado::getApellido);

// Aplicar a lista:
empleados.sort(comp);

// O en Stream:
empleados.stream()
    .sorted(comp)
    .forEach(System.out::println);
Comparable vs Comparator: Comparable define el orden por defecto dentro de la clase. Comparator define estrategias externas fuera de la clase sin tocar su código.
Slide 5 / 7 • Object Identity

The equals() Contract: 5 Inviolable Rules

Reflexive, symmetric, transitive, consistent, and null-safe. The subtle trap of breaking symmetry using instanceof with subclasses.

Los 5 Mandamientos de equals(Object o) Java Language Spec
  • 1
    Reflexiva: x.equals(x) debe retornar true siempre.
  • 2
    Simétrica: x.equals(y) debe retornar lo mismo que y.equals(x).
  • 3
    Transitiva: Si x.equals(y) y y.equals(z), entonces x.equals(z).
  • 4
    Consistente: Múltiples llamadas retornan lo mismo salvo que se mute el objeto.
  • 5
    Null-check: x.equals(null) debe retornar false (nunca lanzar NullPointerException).

La Trampa Mortal de `instanceof` en Subclases

// Punto(x, y) usa instanceof
Punto p = new Punto(1, 2);
PuntoColor pc = new PuntoColor(1, 2, "Rojo");

// ¡Rompe la simetría!
p.equals(pc);  // true (pc es instanceof Punto)
pc.equals(p);  // false (p no tiene color)

// Solución canónica:
if (this.getClass() != obj.getClass()) 
    return false;
Regla arquitectónica: No hay forma perfecta de extender una clase instanciable y agregarle un nuevo componente de valor preservando la simetría y transitividad de `equals` si se usa `instanceof`. Preferí composición sobre herencia.
Slide 6 / 7 • The Golden Rule

The Golden Contract: equals and hashCode

If two objects are equal, their hashCodes MUST match. Breaking this rule causes objects to vanish inside HashSets and HashMaps.

Búsqueda en HashSet: set.contains(new Producto("A1", 50)) Modo: Con Violación de Hash
Bucket [0]
vacio
Bucket [1]
Producto("A1") addr: 0x9AF1
Bucket [2]
vacio
Bucket [3]
Sobrescribiste equals() pero NO hashCode(). El objeto insertado usó su dirección de memoria (Bucket 1).

El Contrato Formal de Java

1. Si dos objetos son iguales por equals():
a.equals(b) == true ⇒ a.hashCode() == b.hashCode() (MANDATORIO).
2. Si dos objetos tienen diferente hashCode():
Son distintos con total certeza. El Set ni siquiera revisa el bucket.
3. Si tienen el mismo hashCode() pero son distintos:
Es una colisión normal. Se resuelve comparando con equals() dentro del bucket.
Slide 7 / 7 • Silent Memory Leaks

Mutable Keys in Hash Collections: The Trapped Object

Mutating fields on an object already stored in a hash collection changes its computed hash without relocating its bucket, stranding it permanently.

El Objeto Fantasma en el Bucket Fuga de Memoria
1. Inserción Inicial:

p = new Persona("Carlos"); set.add(p);
"Carlos".hashCode() apunta al Bucket [2]. El objeto descansa ahí.

2. Mutación Prohibida:

p.setNombre("Alberto");
El objeto muta en el Heap. Ahora su nuevo hash apunta al Bucket [6], pero el objeto sigue físicamente atrapado en la cubeta 2.

3. Búsqueda y Borrado Rotos:

set.contains(p) calcula el hash de "Alberto", busca en Bucket 6 (¡está vacío!). Retorna false. Tampoco podés borrarlo con set.remove(p).

Presiona simular para observar cómo mutar una propiedad rompe la colección hash.

Regla de Diseño Inviolable

Claves Inmutables Siempre:
Toda clase utilizada como clave en un Map o elemento en un Set debe ser inmutable (o al menos los campos que participan en hashCode() y equals() deben ser final).
// Diseño seguro: Java 14+ Record
public record UsuarioId(String codigo, int sucursal) {
    // Inmutable por diseño: 
    // equals() y hashCode() generados de forma 
    // coherente y determinística.
}