Los genéricos (Generics) permiten escribir clases y métodos que funcionan con cualquier tipo, con verificación en tiempo de compilación. Eliminan los casts explícitos y detectan errores de tipo antes de ejecutar el programa.
Por qué existen los genéricos
Antes de Java 5, las colecciones trabajaban con Object. Esto obligaba a hacer casts y permitía mezclar tipos sin que el compilador protestara:
// Sin genéricos (Java pre-5) — propenso a ClassCastException en runtime
List lista = new ArrayList();
lista.add("Hola");
lista.add(42); // el compilador no se queja
String s = (String) lista.get(1); // ClassCastException en ejecución!
// Con genéricos (Java 5+) — el compilador detecta el error
List lista = new ArrayList<>();
lista.add("Hola");
lista.add(42); // ERROR en compilación: incompatible types
String s = lista.get(0); // sin cast necesario
Clases genéricas
public class Caja{ private T contenido; public Caja(T contenido) { this.contenido = contenido; } public T abrir() { return contenido; } public void poner(T nuevo) { this.contenido = nuevo; } @Override public String toString() { return "Caja[" + contenido + "]"; } } Caja cajaTexto = new Caja<>("Hola Java"); Caja cajaNumero = new Caja<>(42); String texto = cajaTexto.abrir(); // sin cast Integer num = cajaNumero.abrir();
Métodos genéricos
// Método que intercambia dos elementos de un array public staticvoid intercambiar(T[] arr, int i, int j) { T temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } String[] palabras = {"Hola", "Mundo", "Java"}; intercambiar(palabras, 0, 2); // palabras: ["Java", "Mundo", "Hola"] // Método que devuelve el máximo de dos comparables public static > T maximo(T a, T b) { return a.compareTo(b) >= 0 ? a : b; } System.out.println(maximo(10, 20)); // 20 System.out.println(maximo("Ana", "Luis")); // Luis
Wildcards: ? extends y ? super
// ? extends T: lee de una colección de T o subtipo (covariante)
public static double sumarNumeros(List extends Number> lista) {
double total = 0;
for (Number n : lista) total += n.doubleValue();
return total;
}
List enteros = List.of(1, 2, 3);
List decimales = List.of(1.5, 2.5);
sumarNumeros(enteros); // OK
sumarNumeros(decimales); // OK
// ? super T: escribe en una colección de T o supertipo (contravariante)
public static void llenar(List super Integer> lista, int cantidad) {
for (int i = 0; i < cantidad; i++) lista.add(i);
}
Borrado de tipos (Type Erasure)
Los genéricos en Java son solo en tiempo de compilación. En tiempo de ejecución, la JVM trabaja con Object (o el bound si hay extends). Esto tiene consecuencias: no puedes crear arrays de genéricos (new T[10] no compila), ni hacer instanceof sobre tipos genéricos (if (obj instanceof List<String>) no compila).
Records (Java 16+): la alternativa moderna a clases de datos
Los records son clases inmutables de datos que generan automáticamente constructor, getters, equals, hashCode y toString:
// Definición de un record
public record Punto(double x, double y) {
// Método de instancia personalizado
public double distanciaAlOrigen() {
return Math.sqrt(x * x + y * y);
}
}
// Uso
Punto p = new Punto(3.0, 4.0);
System.out.println(p.x()); // 3.0 (getter generado)
System.out.println(p.y()); // 4.0
System.out.println(p.distanciaAlOrigen()); // 5.0
System.out.println(p); // Punto[x=3.0, y=4.0]
Punto p2 = new Punto(3.0, 4.0);
System.out.println(p.equals(p2)); // true (equals generado)
Los records son perfectos para DTOs (Data Transfer Objects), resultados de consultas, eventos y cualquier clase cuyo propósito es solo transportar datos.
