Saltearse al contenido

Métodos genéricos en Java

Consideremos el problema de escribir un método que tome un array de objetos de cualquier tipo y una caja del mismo tipo, y ponga todos los objetos del array dentro de la caja. Aquí está un primer intento:

1
public static void deArrayACaja(Object[] array, Caja<Object> caja) {
2
for (Object obj : array) {
3
caja.set(obj);
4
}
5
}

Este método funciona, pero tiene una limitación importante: solo puede manejar Caja<Object>. No podemos usarlo con cajas de tipos más específicos, como Caja<String> o Caja<Integer>.

Para solucionar esto, podemos usar un método genérico. Al igual que las declaraciones de clases genéricas, las declaraciones de métodos también pueden ser genéricas, es decir, parametrizadas por uno o más parámetros de tipo.

1
public static <T> void deArrayACaja(T[] array, Caja<T> caja) {
2
for (T obj : array) {
3
caja.set(obj);
4
}
5
}

Observemos la declaración <T> antes del nombre del método. Esto significa que T es un parámetro de tipo formal del método. Dentro del método, T se comporta como un tipo normal y se puede usar en declaraciones de variables, parámetros y retornos.

Ahora podemos llamar a este método genérico con cualquier tipo de array y caja compatible:

1
String[] arrayString = { "Hola", "Mundo" };
2
Caja<String> cajaString = new Caja<>();
3
deArrayACaja(arrayString, cajaString); // T inferido como String
4
5
Integer[] arrayInteger = { 1, 2, 3 };
6
Caja<Integer> cajaInteger = new Caja<>();
7
deArrayACaja(arrayInteger, cajaInteger); // T inferido como Integer

Observemos que no tenemos que especificar explícitamente el parámetro de tipo T cuando llamamos al método. El compilador de Java infiere el tipo de T basado en los tipos de los argumentos proporcionados.

Métodos Genéricos vs Comodines

Una pregunta que surge es: ¿cuándo debemos usar métodos genéricos y cuándo comodines? La respuesta depende de si hay una dependencia entre los tipos involucrados.

Si hay una dependencia entre los tipos de uno o más argumentos del método y/o su tipo de retorno, entonces se debe usar un método genérico. Por ejemplo, el método deArrayACaja requiere que el tipo del array y el tipo de la caja sean el mismo, por lo que se necesita un método genérico.

Sin embargo, si no hay tal dependencia, es preferible utilizar comodines en lugar de métodos genéricos. Los comodines son más concisos y expresan mejor la idea de subtipificación flexible.

Veamos un ejemplo de un método que puede usar comodines en lugar de un método genérico:

1
public static void inspeccionarCaja(Caja<?> caja) {
2
Object obj = caja.get();
3
// Inspeccionar obj
4
}

Este método simplemente lee el contenido de una caja y lo trata como un Object. No hay dependencia entre los tipos involucrados, por lo que un comodín ? es suficiente.

Es posible combinar métodos genéricos y comodines en la misma declaración de método. Esto puede ser útil cuando hay dependencias parciales entre los tipos involucrados.