Generics en Java: para qué sirven y cómo usarlos
Generics (genéricos) — una de las capacidades clave del lenguaje Java, introducida en la versión J2SE 5.0. Permiten crear clases, interfaces y métodos que trabajan con diferentes tipos de datos, manteniendo una tipificación estricta en la etapa de compilación. En este artículo analizaremos para qué sirven los genéricos, cómo usarlos correctamente y qué errores ayudan a evitar.
¿Para qué sirven los Generics en Java?
Antes de la aparición de los genéricos, los programadores usaban conversión de tipos (casting) y trabajaban con Object. Esto generaba dos problemas principales:
- Falta de seguridad de tipos — se podía agregar cualquier tipo de objeto a una colección, y el error solo se manifestaba en tiempo de ejecución.
- Necesidad de conversión explícita — al extraer un elemento de la colección se requería un cast, lo que saturaba el código.
// Sin Generics (Java anterior a 5)List list = new ArrayList();list.add("cadena");list.add(123); // Error no en etapa de compilaciónString s = (String) list.get(0); // Hay que convertir explícitamente// Con GenericsList<String> list = new ArrayList<>();list.add("cadena");// list.add(123); // ¡Error de compilación!String s = list.get(0); // No se necesita conversiónPrincipales ventajas de Generics:
- Seguridad de tipos — los errores de tipo se detectan en la etapa de compilación.
- Eliminación de conversiones explícitas — el código se vuelve más limpio y legible.
- Reutilización de código — una misma implementación funciona con diferentes tipos.
Creación de clases y métodos genéricos
Clase genérica
Para crear una clase que funcione con un tipo arbitrario, basta con indicar el parámetro de tipo entre corchetes angulares después del nombre de la clase:
public class Box<T> { private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("¡Hola, Java!"); System.out.println(stringBox.getContent());
Box<Integer> intBox = new Box<>(); intBox.setContent(42); System.out.println(intBox.getContent()); }}En este ejemplo, T es el parámetro de tipo. Al crear el objeto Box<String>, todas las ocurrencias de T se reemplazan por String.
Método genérico
Los métodos también pueden ser parametrizados. El parámetro de tipo se indica antes del tipo de retorno:
public class Util { public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); }
public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"A", "B", "C"};
printArray(intArray); // 1 2 3 4 5 printArray(strArray); // A B C }}Observe: el parámetro de tipo <T> va antes de void. Esto es obligatorio para métodos genéricos.
Tipos acotados (Bounded Type Parameters)
A veces es necesario limitar qué tipos pueden usarse como parámetro. Para esto se utilizan las palabras clave extends y super.
Límite superior (extends)
Limita el tipo por arriba — solo se puede usar la clase indicada o sus subclases:
public class Calculator<T extends Number> { private T value;
public Calculator(T value) { this.value = value; }
public double square() {