Generics en Java: para qué sirven y cómo usarlos para principiantes
Al estudiar Java, tarde o temprano te encontrarás con el concepto de Generics (genéricos, tipos parametrizados). Para un principiante, esto puede parecer complejo, pero en realidad es una de las herramientas más útiles del lenguaje. En este artículo, analizaremos qué son los Generics, para qué sirven y cómo usarlos correctamente. El material está dirigido a desarrolladores principiantes.
¿Qué son los Generics y por qué es malo sin ellos?
Generics (tipos parametrizados) es un mecanismo de Java que permite parametrizar tipos. En palabras simples, puedes escribir una clase, interfaz o método que funcione con diferentes tipos de datos, pero que mantenga una tipificación estricta en tiempo de compilación.
Imagina que quieres crear una caja en la que puedas poner cualquier objeto. Sin Generics, tendrías que usar Object:
public class Box { private Object item;
public void put(Object item) { this.item = item; }
public Object get() { return item; }}Usar una clase así es un verdadero suplicio:
Box box = new Box();box.put("Hola");// Al extraer, es necesario hacer un casting explícitoString text = (String) box.get(); // ¡Riesgo de ClassCastException!Si accidentalmente pones un número y luego intentas convertirlo a cadena, el programa fallará con un error en tiempo de ejecución. Los Generics resuelven este problema trasladando la verificación de tipos a la etapa de compilación.
Cómo usar Generics: sintaxis básica
Reescribamos nuestra clase Box usando Generics:
public class Box<T> { private T item;
public void put(T item) { this.item = item; }
public T get() { return item; }}Aquí, <T> es el parámetro de tipo. T es un nombre comúnmente aceptado (de la palabra "Type"), pero puedes usar cualquier letra o palabra: E (Element), K (Key), V (Value), etc.
Ahora el uso se ha vuelto seguro y elegante:
Box<String> stringBox = new Box<>();stringBox.put("Hola");String text = stringBox.get(); // ¡No es necesario hacer casting!
// Esta línea no compilará:// stringBox.put(123); // ¡Error de compilación!El compilador sabe que stringBox solo funciona con cadenas. Si intentas poner un número, el código ni siquiera compilará: te enterarás del error de inmediato, no en tiempo de ejecución.
Generics en colecciones: dónde más se necesitan
El lugar más popular para usar Generics son las colecciones de Java (List, Set, Map y otras). Antes de la llegada de los genéricos (en Java 5), los desarrolladores sufrían con ArrayList, que almacenaba Object:
// Estilo antiguo (sin Generics)List list = new ArrayList();list.add("Hola");list.add(123); // ¡Esto también se puede agregar!
String item = (String) list.get(0); // NormalString item2 = (String) list.get(1); // ClassCastException!Con Generics, todo es más simple y seguro:
List<String> names = new ArrayList<>();names.add("Alice");names.add("Bob");// names.add(123); // Error de compilación
for (String name : names) { System.out.println(name.toUpperCase()); // ¡Sin ningún casting!}Lo mismo funciona para Map:
Map<String, Integer> ageMap = new HashMap<>();ageMap.put("Alice", 30);ageMap.put("Bob", 25);
int aliceAge = ageMap.get("Alice"); // Desempaquetado automático a intRestricciones y wildcards: cómo hacer los Generics flexibles
A veces necesitas restringir qué tipos puede aceptar un genérico. Para esto se utilizan los bounded type parameters (parámetros de tipo acotados).
Por ejem