Genéricos en Java: cómo dejar de temer a los corchetes angulares y empezar a escribir código confiable
\n\nEn el mundo de Java hay una cosa que asusta a los principiantes casi más que la concurrencia: los corchetes angulares <>. Generics (también conocidos como genéricos o tipos parametrizados) parecen misteriosos, pero en realidad son tu mejor aliado en la lucha por un código limpio y seguro. Analicemos cómo funciona el tipado en Java con ejemplos que podrás aplicar hoy mismo.
Imagina: escribes un método que debe funcionar tanto con números, como con cadenas y con tus propias clases. Sin genéricos, tendrías que crear decenas de métodos sobrecargados o usar Object y verificar los tipos manualmente constantemente. Los tipos parametrizados de Java resuelven este problema de manera elegante y segura.
¿Por qué sin genéricos el código se convierte en un campo minado?
\n\nRecordemos cómo vivían los programadores antes de la llegada de los genéricos (antes de Java 5). Si alguna vez escribiste un ArrayList sin especificar el tipo, conoces esta pesadilla:
List list = new ArrayList();\nlist.add("Hola");\nlist.add(42);\nlist.add(new Object());\n\n// En algún lugar, 100 líneas después:\nString text = (String) list.get(1); // ¡ClassCastException en tiempo de ejecución!\n\nEl problema clásico: ClassCastException — un error que solo se manifiesta durante la ejecución. Podrías pasar la lista a otro método, y alguien allí está seguro de que solo contiene cadenas. ¡Y pum! La aplicación se cae. Los genéricos en Java trasladan la verificación de tipos a la etapa de compilación — es como un seguro que se activa antes de ponerte al volante.
\n\nGenerics en Java: sintaxis básica que debes conocer
\n\nEn el corazón de los genéricos hay una idea simple: parametrizamos una clase o método con un tipo. En lugar de un tipo concreto, escribimos un parámetro de tipo (generalmente una letra mayúscula: T, E, K, V).
El ejemplo más popular son las colecciones de Java:
\n\n// Sin genérico — peligroso\nList rawList = new ArrayList();\n\n// Con genérico — seguro\nList<String> safeList = new ArrayList<>();\nsafeList.add("Java");\nsafeList.add("Generics");\n// safeList.add(123); // ¡Error de compilación!\n\nAhora el compilador sabe: dentro de safeList solo puede haber cadenas. Intentar agregar un número provocará un error en la compilación, no en tiempo de ejecución. Esto es la seguridad de tipos en Java en acción.
Creando tu primera clase genérica
\n\nSupongamos que escribimos un contenedor simple para almacenar un valor. Sin genéricos, tendríamos que usar Object:
public class Box {\n private Object value;\n\n public void set(Object value) { this.value = value; }\n public Object get() { return value; }\n}\n\nY ahora — con genérico:
\n\npublic class Box<T> {\n private T value;\n\n public void set(T value) { this.value = value; }\n public T get() { return value; }\n}\n\n// Uso:\nBox<Integer> intBox = new Box<>();\nintBox.set(100);\nint value = intBox.get(); // ¡Sin cast!\n\nObserva: ya no necesitamos escribir (Integer) intBox.get(). El compilador sabe por sí mismo que get() devolverá un Integer. Esta es la magia de los tipos parametrizados de Java.
Tipado en Java: restricciones y wildcards
\n\nLos genéricos serían demasiado simples si no hubiera matices. El principal son los bounded type parameters (parámetros de tipo acotados). A veces es necesario decir: "El tipo T debe ser un número" o "T debe implementar la interfaz Comparable".
\n\n// T solo puede ser Number o una subclase suya\npublic class Calculator<T extends Number> {\n private T value;\n\n public double square() {\n return value.doub