Menu

Genéricos en Java: clases y métodos con seguridad de tipos

Qué son los genéricos de Java, cómo escribir clases y métodos genéricos, parámetros de tipo acotados, comodines y por qué importa el borrado de tipos.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Por qué existen los genéricos

Un tipo genérico te permite escribir el código una vez y reutilizarlo para muchos tipos sin renunciar a la seguridad de tipos. En lugar de escribir una IntBox, una StringBox y una UserBox por separado, escribes una sola Box<T> donde T es un marcador de posición que rellena quien llama.

Ya has usado genéricos cada vez que escribiste ArrayList<String> o HashMap<String, Integer>. La parte <...> es un argumento de tipo. Esta página muestra cómo escribir los tuyos.

La alternativa - almacenar todo como Object - desecha la información de tipos y obliga a hacer conversiones feas y propensas a errores:

Esa conversión de la última línea lanza una ClassCastException en tiempo de ejecución - justo el tipo de error que los genéricos están diseñados para hacer imposible.

Una clase genérica

Declara un parámetro de tipo entre corchetes angulares después del nombre de la clase. Por convención es una sola letra mayúscula: T de "type" (tipo), E de "element" (elemento), K/V para clave/valor.

Dentro de Box, cada T se convierte en lo que haya indicado quien llama. Box<String> es una caja que solo guarda y devuelve String. El compilador rechaza name.set(99) antes de que el programa siquiera se ejecute.

Los <> vacíos de la derecha (el operador diamante) permiten que el compilador infiera el argumento de tipo a partir del lado izquierdo, así no repites <String> dos veces.

Métodos genéricos

Un solo método puede tener su propio parámetro de tipo, independiente de la clase. Pon el parámetro <T> antes del tipo de retorno:

Nunca pasas T de forma explícita - el compilador lo infiere a partir del argumento. Los métodos genéricos son la forma en que utilidades como Collections.sort o List.of mantienen la seguridad de tipos con cualquier tipo de elemento.

Parámetros de tipo acotados

A veces un tipo genérico solo tiene sentido para algunos tipos. extends restringe el parámetro para que puedas llamar a los métodos de la cota. Aquí T extends Number significa que T es Number o cualquier subclase (Integer, Double, ...), por lo que doubleValue() está disponible:

Ten en cuenta que aquí extends significa "es un subtipo de", y funciona tanto para clases como para interfaces - <T extends Comparable<T>> es extremadamente común cuando necesitas comparar elementos.

Comodines: ? extends y ? super

Un detalle sutil: List<Integer> no es una List<Number>, aunque Integer sea un Number. Los genéricos son invariantes. Los comodines relajan esto cuando solo necesitas leer o solo necesitas escribir.

Usa ? extends T para un productor del que lees, y ? super T para un consumidor en el que escribes (la regla "PECS" - Producer Extends, Consumer Super):

Una lista ? extends Number te deja leer los elementos como Number pero no añadir a ella (el compilador no puede saber el tipo exacto del elemento). Una lista ? super Integer te deja añadir Integer, pero las lecturas vuelven como Object. Elige el comodín que coincida con cómo fluyen los datos.

El borrado de tipos y sus límites

Los genéricos son una característica de tiempo de compilación. Tras compilar, el parámetro de tipo se borra - en tiempo de ejecución Box<String> y Box<Integer> son ambas simplemente Box. Esto mantiene los genéricos compatibles hacia atrás con código antiguo, pero impone límites reales.

// Ninguno de estos compila - el parámetro de tipo no existe en tiempo de ejecución:
T value = new T();          // no se puede instanciar un parámetro de tipo
T[] array = new T[10];      // no se puede crear un arreglo genérico
if (list instanceof List<String>) { } // no se puede comprobar el argumento de tipo

Como el tipo desaparece en tiempo de ejecución, no puedes preguntar "¿qué era T?" mediante reflexión, ni puedes sobrecargar métodos que difieren solo por su argumento genérico (foo(List<String>) y foo(List<Integer>) se borran a la misma firma). Cuando realmente necesitas el tipo en tiempo de ejecución, pasa un token Class<T> como parámetro del constructor o del método.

Siguiente: expresiones lambda

Viste que un método genérico toma un tipo como parámetro. El siguiente paso es tratar el comportamiento como un parámetro. Las expresiones lambda te permiten pasar un fragmento de código - una función - a un método, que es exactamente como ordenarás, filtrarás y transformarás las colecciones genéricas que acabas de aprender a tipar con seguridad.

Preguntas frecuentes

¿Qué son los genéricos en Java?

Los genéricos te permiten escribir una clase o un método que funciona con un tipo que especificas más tarde, en lugar de atarlo a un tipo concreto. Declaras un parámetro de tipo entre corchetes angulares - class Box<T> - y quien llama indica el tipo real - Box<String>. El compilador hace cumplir ese tipo en todas partes, así que detectas las incompatibilidades en tiempo de compilación y te ahorras las conversiones manuales.

¿Por qué usar genéricos en lugar de Object?

Usar Object pierde toda la información de tipos: el compilador no puede impedir que metas algo incorrecto y debes convertir cada valor que sale (con riesgo de ClassCastException en tiempo de ejecución). Los genéricos trasladan esa comprobación al momento de compilación. Una List<String> simplemente no aceptará un Integer, y get() ya devuelve un String - sin conversiones ni sorpresas en tiempo de ejecución.

¿Qué es el borrado de tipos en los genéricos de Java?

El borrado de tipos significa que la información del tipo genérico solo existe en tiempo de compilación. Tras compilar, List<String> y List<Integer> son ambas simplemente List en tiempo de ejecución - el parámetro de tipo se borra. Por eso no puedes escribir new T[10], llamar a list instanceof List<String> ni leer un parámetro de tipo mediante reflexión. Los genéricos te dan seguridad en tiempo de compilación, no datos de tipo en tiempo de ejecución.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR