Introducción: El problema que resuelven los Generics
Imagine que escribe un método para ordenar una lista de números. Lo escribe para Integer y, una semana después, necesita ordenar una lista de String. Tiene que crear un nuevo método o usar Object y hacer conversiones de tipo constantemente. Esto es incómodo, inseguro y provoca errores en tiempo de ejecución.
Este es precisamente el problema que resuelven los Generics (genéricos) en Java. Permiten crear clases, interfaces y métodos que trabajan con diferentes tipos de datos, pero manteniendo una tipificación estricta en tiempo de compilación. Sin los genéricos, el Java moderno sería mucho menos cómodo y seguro.
En este artículo, analizaremos para qué sirven los Generics, cómo están estructurados y, lo más importante, cómo aplicarlos con ejemplos reales.
1. Fundamentos de Generics: qué son y cómo funcionan
1.1. El problema de los tipos "crudos" (raw types)
Antes de la llegada de Java 5 (2004), los desarrolladores usaban colecciones sin especificar el tipo:
List list = new ArrayList();list.add("Hola");list.add(123);// Al recuperar, hay que recordar qué tipo contieneString text = (String) list.get(0); // OKInteger num = (Integer) list.get(1); // OK// Pero si nos equivocamos — ClassCastException en runtimeString error = (String) list.get(1); // ¡Error!Este código es peligroso: el error solo se detecta al ejecutar el programa.
1.2. Cómo funcionan los Generics
Los Generics permiten especificar el tipo de datos con el que trabajará una clase o método, directamente entre corchetes angulares <T>. El compilador verifica los tipos en tiempo de compilación y aplica automáticamente las conversiones correctas.
Sintaxis básica:
// Declaración de una clase genéricapublic class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; }}
// UsoBox<String> stringBox = new Box<>(); // <> — operador diamante (Java 7+)stringBox.setContent("Hola");String text = stringBox.getContent(); // ¡Sin conversión!
Box<Integer> intBox = new Box<>();intBox.setContent(42);int number = intBox.getContent(); // ¡Sin conversión!
// stringBox.setContent(123); // ¡Error de compilación!1.3. Ventajas en la práctica
- Seguridad de tipos: los errores se detectan en tiempo de compilación, no en runtime.
- Eliminación de conversiones explícitas: el código es más limpio y legible.
- Reutilización de código: una misma clase/método funciona con cualquier tipo.
2. Generics en la práctica: clases, métodos e interfaces
2.1. Clases genéricas
El caso más común son las colecciones. Pero usted puede crear sus propias clases genéricas:
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}
// UsoPair<String, Integer> pair = new Pair<>("Edad", 30);String key = pair.getKey();Integer value = pair.getValue();2.2. Métodos genéricos
Un método puede tener sus propios parámetros de tipo, no relacionados con la clase:
public class Utils { // Método genérico para crear una lista a partir de un array public static <T> List<T> arrayToList(T[] array) { List<T> list = new ArrayList<>(); for (T element : array) { list.add(element); } return list; }}
// UsoString[] names = {"Ana", "Boris", "Víctor"};List<String> nameList = Utils.arrayToList(names);