Saltar a contenido

Generics Avanzados

Los generics permiten crear clases, interfaces y métodos parametrizados por tipos, proporcionando type safety en tiempo de compilación.

¿Por qué usar Generics?

// Sin generics: código inseguro
List lista = new ArrayList();
lista.add("texto");
String s = (String) lista.get(0); // Cast necesario, puede fallar en runtime

// Con generics: type safety
List<String> lista = new ArrayList<>();
lista.add("texto");
String s = lista.get(0); // No necesita cast, seguro en compilación

Wildcards (Comodines)

Unbounded Wildcard (?)

Cuando el tipo específico no importa, solo operaciones que funcionan para cualquier tipo.

// Imprime cualquier lista
public static void printList(List<?> list) {
    for (Object elem : list) {
        System.out.println(elem);
    }
}

// Uso
printList(Arrays.asList(1, 2, 3));      // List<Integer>
printList(Arrays.asList("a", "b"));     // List<String>

⚠️ No puedes: Agregar elementos (excepto null), porque no sabes el tipo exacto.

Upper Bounded Wildcard (? extends T)

Acepta T o cualquier subclase de T. Producer (produce elementos para leer).

// Lee números de cualquier lista numérica
public static double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number n : numbers) {
        total += n.doubleValue();
    }
    return total;
}

// Uso
sum(Arrays.asList(1, 2, 3));                    // List<Integer>
sum(Arrays.asList(1.5, 2.5, 3.5));             // List<Double>

Lower Bounded Wildcard (? super T)

Acepta T o cualquier superclase de T. Consumer (consume elementos para agregar).

// Agrega enteros a cualquier lista que acepte Integer o sus superclases
public static void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

// Uso
List<Number> numbers = new ArrayList<>();
addNumbers(numbers); // OK: Number es super de Integer

List<Object> objects = new ArrayList<>();
addNumbers(objects); // OK: Object es super de Integer

Principio PECS

Producer Extends, Consumer Super

// Producer: solo lee elementos (extends)
public static <T> void copyFrom(List<T> dest, List<? extends T> src) {
    for (T item : src) {  // Produce elementos
        dest.add(item);
    }
}

// Consumer: solo agrega elementos (super)
public static <T> void copyTo(List<? super T> dest, List<T> src) {
    for (T item : src) {
        dest.add(item);   // Consume elementos
    }
}

// Uso
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();

// Producer: integers produce elementos
copyTo(numbers, integers);   // OK: Number super de Integer
copyTo(objects, integers);   // OK: Object super de Integer

// Consumer: numbers consume elementos
copyFrom(numbers, integers); // OK: Integer extends Number

Type Parameters con Bounds

Single Bound

// Solo tipos que extienden Number
public class Calculator<T extends Number> {
    private T value;

    public double getDoubleValue() {
        return value.doubleValue();
    }
}

// Uso válido
Calculator<Integer> calc1 = new Calculator<>();
Calculator<Double> calc2 = new Calculator<>();

// Uso inválido
// Calculator<String> calc3 = new Calculator<>(); // Error: String no extiende Number

Multiple Bounds

// T debe extender Number Y implementar Comparable
public static <T extends Number & Comparable<T>> T findMax(List<T> list) {
    T max = list.get(0);
    for (T item : list) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

// Uso
Integer max = findMax(Arrays.asList(1, 5, 3, 9, 2)); // 9

Type Erasure

Los generics solo existen en tiempo de compilación. En runtime, todo se convierte a Object o al primer bound.

// Código fuente
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();

// Después de compilación (erasure)
List strings = new ArrayList();
List integers = new ArrayList();

// Verificación
System.out.println(strings.getClass() == integers.getClass()); // true

Consecuencias de Type Erasure

// No puedes crear arrays de tipos genéricos
// List<String>[] array = new List<String>[10]; // Error de compilación

// No puedes usar instanceof con tipos específicos
// if (obj instanceof List<String>) // Error de compilación

// No puedes hacer cast seguro sin unchecked warning
List<?> wildcard = new ArrayList<String>();
// List<String> list = (List<String>) wildcard; // Warning: unchecked cast

Generic Methods

Métodos genéricos independientes de la clase.

public class Utils {
    // Método genérico estático
    public static <T> T getFirst(List<T> list) {
        return list.isEmpty() ? null : list.get(0);
    }

    // Método genérico con bounds
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }

    // Varargs con generics (cuidado)
    @SafeVarargs
    public static <T> List<T> asList(T... elements) {
        return Arrays.asList(elements);
    }
}

// Uso
String first = Utils.getFirst(Arrays.asList("a", "b", "c"));
String maxStr = Utils.max("apple", "banana");

Implementación en el proyecto

// AdvancedGenericsExample.java
public class AdvancedGenericsExample {

    // PECS principle
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (T item : src) {
            dest.add(item);
        }
    }

    // Bounded type parameter
    public static <T extends Number> double sum(List<T> numbers) {
        return numbers.stream()
            .mapToDouble(Number::doubleValue)
            .sum();
    }
}

Escenarios BDD

Escenario: Usar wildcard extends para lectura
  Dado una lista de números enteros
  Cuando calculo la suma usando wildcard extends
  Entonces obtengo la suma correcta

Escenario: Usar wildcard super para escritura
  Dado una lista de Objects
  Cuando agrego enteros usando wildcard super
  Entonces los enteros se agregan correctamente

Escenario: Bounded type parameter
  Dado una lista de números comparables
  Cuando busco el máximo
  Entonces encuentro el número mayor

Resumen de Wildcards

Wildcard Lee (Producer) Escribe (Consumer) Uso típico
? Sí (Object) No (solo null) Operaciones genéricas
? extends T Sí (T) No Producir elementos
? super T Sí (Object) Sí (T) Consumir elementos
T Sí (T) Sí (T) Ambas operaciones

Conclusión

Los generics avanzados (wildcards y bounds) permiten escribir código flexible y type-safe. Recuerda PECS: Producer Extends, Consumer Super.