java-genericos-generics-wildcards

Genéricos en Java: tipos parametrizados y wildcards

  • 3 min

Los genéricos permiten parametrizar clases, interfaces y métodos con tipos comprobados en compilación.

Los Genéricos permiten ponerle una “etiqueta” a la clase para decirle al compilador exactamente qué tipo de datos vamos a manejar.

// Código con Genéricos
ArrayList<String> caja = new ArrayList<>(); // El "Diamante" <>

caja.add("Hola");
// caja.add(123); // ¡ERROR DE COMPILACIÓN! El IDE lo subraya en rojo.
Copied!

Gracias a los genéricos, convertimos errores de ejecución (que crashean la app) en errores de compilación (que arreglas antes de entregar).

Creando tus propias clases genéricas

No solo puedes usar genéricos, puedes crear tus propias clases que se adapten a cualquier tipo. Usamos T (por convención de “Type”) como un marcador de posición.

// Definimos una clase que maneja un tipo "T" desconocido aún
public class Caja<T> {
    private T contenido;

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

    public T sacar() {
        return contenido;
    }
}
Copied!

Ahora podemos reutilizar esta lógica para cualquier cosa:

// Una caja de Strings
Caja<String> cajaTexto = new Caja<>();
cajaTexto.guardar("Secreto");

// Una caja de Enteros (Reutilizamos la misma clase)
Caja<Integer> cajaNumeros = new Caja<>();
cajaNumeros.guardar(42);
Copied!

Nomenclatura común:

  • E: Elemento (usado en Colecciones).
  • K, V: Key, Value (usado en Mapas).
  • T: Tipo genérico.

Comodines (wildcards) ? y la invarianza

Aquí es donde la cabeza suele doler un poco. Presta atención.

Sabemos que String es hijo de Object. Entonces, ¿es List<String> hijo de List<Object>?

La respuesta es NO. En Java, los genéricos son Invariantes.

List<String> listaStrings = new ArrayList<>();
List<Object> listaObjetos = listaStrings; // ¡ERROR DE COMPILACIÓN!
Copied!

¿Por qué?

Si Java permitiera eso, podrías hacer esto:

  1. Pasas tu lista de Strings a una variable List<Object>.
  2. Como es una lista de objetos, le añades un Integer.
  3. Ahora tu lista original de Strings tiene un número dentro. ¡Has roto la seguridad!

El comodín ? (the wildcard)

A veces necesitamos métodos que acepten “una lista de cualquier cosa”. Para eso usamos la interrogación ?.

// Acepta una lista de CUALQUIER tipo, pero será de solo lectura (limitado)
public void imprimirLista(List<?> lista) {
    for (Object o : lista) {
        System.out.println(o);
    }
}
Copied!

Comodines acotados (extends)

A veces queremos ser más específicos: “Acepto cualquier lista, siempre que sean Números (Integer, Double, Float…)”.

// Upper Bounded Wildcard
public double sumarLista(List<? extends Number> lista) {
    double suma = 0;
    for (Number n : lista) {
        suma += n.doubleValue();
    }
    return suma;
}
Copied!

Esto es muy potente para crear APIs flexibles.

Erasure (borrado de tipos)

Es importante saber que los genéricos en Java son una ilusión del compilador.

Java implementa los genéricos principalmente mediante borrado de tipos: el bytecode ejecutable usa Object o el límite superior y el compilador inserta conversiones y, cuando hacen falta, métodos puente. Parte de la información genérica se conserva como metadatos para reflexión y herramientas.

Esto se llama Type Erasure.

  • Consecuencia: No puedes hacer new T() (porque en ejecución no sabemos qué es T).
  • Consecuencia: No puedes usar tipos primitivos (List<int>). Tienes que usar los Wrappers (List<Integer>).