Java Collections Framework & Genéricos

Colecciones en Java y Genéricos (JCF)

1 de 7
Slide 1 / 7 • Taxonomy & Hierarchy

El Mapa Taxonómico del Java Collections Framework

La arquitectura del JCF se organiza en dos grandes ramas: la jerarquía Collection (elementos individuales) y la jerarquía Map (pares clave-valor).

Jerarquía Interactiva (Hacé click en una colección) JCF Core
<<interface>> Collection<E>
List<E>
Set<E>
Queue<E>
<<interface>> Map<K, V>
Ficha Técnica: ArrayList<E> Familia: List
¿Permite Duplicados? Sí
Orden de Elementos Posicional (por índice)
Acceso por Índice O(1)
Búsqueda por Valor O(N)
Estructura interna: Arreglo dinámico que redimensiona copiando a un nuevo array (+50%) cuando se llena. Es la opción por defecto cuando necesitás acceso rápido por índice.
Slide 2 / 7 • Type Safety

Genéricos: Adiós a los ClassCastException en Runtime

Antes de Java 5, las colecciones guardaban Object, obligando a castear a ciegas en tiempo de ejecución. Los genéricos mueven el error a tiempo de compilación.

Java 1.4: Tipos Crudos (Raw Types) Bomba de Tiempo en Runtime
// Lista sin tipo (acepta Object)
List lista = new ArrayList();
lista.add("Hola mundo");
lista.add(42); // Se cuela un Integer

// Compila sin avisar, pero al leer:
String s1 = (String) lista.get(0); // OK
String s2 = (String) lista.get(1); // ¡BOOM!
// ClassCastException: Integer cannot be cast to String
⚠️ El compilador calla. El fallo explota en el servidor en producción.
Java 5+: Colecciones Parametrizadas Escudo en Compilación
// Lista tipada con genérico <String>
List<String> lista = new ArrayList<>();
lista.add("Hola mundo");

// ¡El compilador bloquea de inmediato!
lista.add(42); 
// ERROR: incompatible types: int cannot be converted to String

String s = lista.get(0); // Sin casteo manual
🛡️ Detección estática instantánea antes de que el código se ejecute.
Slide 3 / 7 • Practical Decision Guide

Las 4 Familias: Árbol de Decisión Interactivo

Elegir la colección correcta define el rendimiento y la legibilidad de tu sistema. Respondé las preguntas para encontrar la estructura ideal.

Asistente de Selección de Colección Paso 1 de 3

1. ¿Tus datos son pares Clave → Valor (ej. DNI → Persona)?

Colección Recomendada ArrayList<T>

La opción por defecto para listas

Si necesitás almacenar elementos individuales, permitís duplicados y requerís acceso rápido por índice posicional, ArrayList es el estándar indiscutido en la industria.

List<String> lista = new ArrayList<>();
lista.add("Elemento");
String item = lista.get(0); // O(1)
Slide 4 / 7 • Under the Hood

Anatomía Interna de HashMap: Cubetas (Buckets) y Hashing

HashMap logra acceso O(1) promedio indexando un array interno mediante el hashCode() de la clave y una operación bit a bit.

Matemática del Hashing: Clave → Índice de Cubeta table.length = 16
1
Clave de entrada: String key = "juan"
2
Cálculo de Hash Code: int h = "juan".hashCode() → 3254921
3
Compresión Bit a Bit (h & (16 - 1)): 3254921 & 15 → Índice: [9]
4
Almacenamiento en Cubeta table[9]: Node("juan", valor, next = null)
¿Por qué potencias de 2? Al ser la capacidad una potencia de dos (16, 32, 64...), la operación h % 16 es idéntica a h & (16 - 1), la cual se ejecuta a nivel de microprocesador en un solo ciclo de reloj.

Estructura del Nodo Interno

// Clase interna de HashMap (Node<K,V>)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;    // Hash precomputado
    final K key;       // Clave original
    V value;           // Valor asociado
    Node<K,V> next;    // Enlace si hay colisión
}

// El mapa es simplemente:
transient Node<K,V>[] table;
Claves inmutables: Si mutás un objeto luego de usarlo como clave en un HashMap, su hashCode() cambia y el mapa nunca más podrá encontrarlo (fuga de memoria silenciosa).
Slide 5 / 7 • Collisions & Trees

Manejo de Colisiones y Treeification (Árboles Rojo-Negro)

Cuando dos claves caen en el mismo bucket ocurre una colisión. Java 8+ transforma la lista enlazada en un árbol rojo-negro si supera 8 elementos.

Evolución de una Cubeta: Lista → Árbol Java 8 Optimization
≤ 8 Nodos: Encadenamiento Lineal O(N)
N1
→
N2
→
N3
→ null
> 8 Nodos: Treeification a Red-Black Tree O(log N)
Raíz (Black)
Hijo Izq
Hijo Der
Ataques DoS prevenidos: En versiones antiguas de Java, atacantes enviaban miles de claves diseñadas para colisionar en el mismo bucket, degradando el HashMap a una lista $O(N)$ y saturando el CPU al 100%. Con árboles balanceados, el peor caso se limita a $O(\log N)$.

Constantes Críticas en HashMap

DEFAULT_INITIAL_CAPACITY 16
DEFAULT_LOAD_FACTOR 0.75
TREEIFY_THRESHOLD 8
UNTREEIFY_THRESHOLD 6

Si un bucket con árbol se reduce a 6 elementos por eliminaciones, Java lo des-arboliza volviendo a lista enlazada para ahorrar memoria.

Slide 6 / 7 • Interactive Live Lab

Simulador Interactivo de HashMap en Vivo

Experimentá agregando claves, forzando colisiones y observando el factor de carga (Load Factor) y el resize() automático.

Cubetas (Buckets) en Memoria Items: 3 | Capacidad: 8 | Carga: 37%
HashMap inicializado con capacidad 8. Ingresá una clave para observar su hashing y asignación.

Métricas de Rendimiento

Factor de Carga Actual
0.375 / Umbral: 0.75
¿Requiere Resize? No (Capacidad suficiente)
Efecto del Resize: Cuando $N > capacidad \times 0.75$, la tabla duplica su tamaño ($8 \to 16 \to 32$) y reubica todos los nodos con el nuevo bitmask.
Slide 7 / 7 • Bytecode & JVM Internals

Type Erasure: La Letra Chica de los Genéricos

Los genéricos existen solo para el compilador. En el bytecode de la JVM desaparecen y se reemplazan por Object con casteos automáticos.

Código Fuente (Java Source) Lo que escribís
public class Caja<T> {
    private T contenido;

    public void guardar(T dato) {
        this.contenido = dato;
    }

    public T obtener() {
        return this.contenido;
    }
}
Bytecode Compilado (Type Erasure) Lo que ve la JVM
public class Caja {
    private Object contenido; // T se borró

    public void guardar(Object dato) {
        this.contenido = dato;
    }

    public Object obtener() {
        return this.contenido;
    }
}

Consecuencias Inevitables del Type Erasure en Java

1. Prohibido new T()

En runtime la JVM no sabe qué constructor llamar porque T ya no existe.

2. Prohibido new T[10]

Los arreglos en Java son reificados (conocen su tipo en runtime); los genéricos no.

3. No hay List<int>

Los primitivos no heredan de Object, exigiendo wrappers como Integer.