3. Las firmas de las clases y métodos genéricos
![]() |
La biblioteca RxJava cuenta con numerosos métodos que aceptan como parámetros instancias de interfaces genéricas. En ocasiones, la firma de estas es compleja. He aquí algunos ejemplos:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
public final <R> Observable<R> flatMap(Func1<? super T,? extends Observable<? extends R>> func)
Estos dos métodos utilizan dos tipos genéricos T y R. Pero, ¿qué significa, por ejemplo, la definición del parámetro del método map: Func1<? super T,? extends R> func ?
La información sobre los genéricos se puede encontrar en URL [https://docs.oracle.com/javase/tutorial/java/generics/]. Parte de la información que aparece a continuación procede de URL. Los genéricos aparecieron con la versión 1.5 de Java.
Imaginemos un servicio web que proporciona información de diferentes tipos. En ocasiones, el servicio web puede no poder proporcionar dicha información y, por lo tanto, debe notificar un error a su cliente. En ese caso, se puede estandarizar la respuesta del servicio web de la siguiente forma:
public class Response<T> {
// ----------------- propiedades
// estado de la operación
private int status;
// posibles mensajes de error
private List<String> messages;
// el cuerpo de la respuesta
private T body;
...
}
- línea 1: la clase [Response] se define mediante el tipo T;
- línea 9: el tipo T es el tipo del cuerpo de la respuesta, que es lo que realmente espera el cliente;
- línea 5: un código de error, 0 si no hay error;
- línea 7: mensajes que explican el error, null si no hay error;
Por parte del cliente, se podrá escribir entonces:
Response<Product> product=getDataFromWebService(...) ;
o
Response<List<Product>>=getDataFromWebService(...) ;
El tipo formal T se sustituye por un tipo efectivo, en este caso [Product] o [List<Product>]. La clase genérica [Response<T>] resulta aquí práctica, ya que permite trabajar con una respuesta estándar.
Para crear un tipo [Response] con el operador new, se escribirá:
Response<List<Product>> response=new Response<List<Product>> (...) ;
A partir de la versión 1.8 de Java (version), la instrucción anterior se puede escribir de forma más sencilla:
Response<List<Product>> response=new Response<> (...) ;
Ya no es necesario especificar a la derecha del signo = el tipo efectivo de los parámetros genéricos: se utilizará el tipo formal especificado a la izquierda del signo =. A esto se le llama inferencia de tipos: el compilador es capaz de determinar por sí mismo el tipo efectivo de los parámetros genéricos a partir del contexto. Esta característica se utiliza ampliamente en las funciones lambda, donde a menudo se omite el tipo efectivo de los parámetros genéricos de un método.
Ahora, en el lado del cliente, el método [getDataFromWebService] podría tener la siguiente firma:
<T1,T2> Response<T1> getDataFromWebService(String urlWebService, String httpMethod, T2 post)
Aquí tenemos un método genérico parametrizado por dos tipos, T1 y T2:
- T1 es el tipo del cuerpo de la respuesta esperada;
- T2 es el tipo del valor enviado para una operación POST, null para una operación GET;
- urlWebService: es el URL del servicio web;
- httpMethod: es el método HTTP, GET o POST que se debe utilizar para consultar este URL;
En el lado del cliente, se puede tener la siguiente llamada:
Long id=... ;
Response<Product> product=this.<Product,Long>getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
En este caso, tendremos T1=Product y T2=Long.
Con Java 8, se puede utilizar la inferencia de tipos y se escribirá de forma más sencilla:
Long id=... ;
Response<Product> product=getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
Aquí hay otra llamada posible:
Long[] ids=... ;
Response<List<Product>> product=getDataFromWebService('http://localhost:8080/rest/products','POST',ids) ;
En este caso tendremos T1=List<Product> y T2=Long[].
La clase o el método genérico pueden imponer restricciones a sus parámetros genéricos. Volvamos a la definición del método [map] de RxJava:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
El método admite dos tipos de parámetros, T y R. El tipo [Func1] es una interfaz genérica:
![]() |
Func1 define una interfaz funcional, es decir, una interfaz que solo tiene un único método. Las interfaces funcionales son la base de las funciones lambda de Java 8. Aquí, el método de la interfaz se define como:
Por lo tanto, T es el tipo del parámetro pasado a [call] y R el tipo del resultado obtenido. Volvamos a la definición del método [map]:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
- el método [map] espera un único parámetro de tipo [Func1<? super T,? extends R>] y devuelve un tipo [Observable<R>];
- ? super T: el parámetro pasado al método [Func1.call] debe ser de tipo T o un supertipo de T;
- ? extends R: el tipo del resultado devuelto por el método [call] debe ser de tipo R o un tipo derivado de R si R es una clase, o bien implementar R si R es una interfaz;
Para comprender mejor las dos restricciones anteriores, veamos algunos ejemplos extraídos de la referencia [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html]:
package tests;
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u) {
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.set(new Integer(10));
integerBox.inspect(new Long(20));
integerBox.inspect(new Double(-1.78));
//integerBox.inspect("some text"); // error: ¡esto sigue siendo String!
}
}
- línea 3: la clase [Box] se parametriza mediante el tipo T, que es el tipo del campo de la línea 5;
- línea 15: el método [inspect] admite un parámetro de tipo U. Un método también puede tener parámetros genéricos. En ese caso, se declara de la siguiente manera:
Aquí, U se declara como tipo genérico del método mediante <U>, pero con una restricción <U extends Number>, es decir, que U debe extender la clase [Number]. Debido a esta restricción, el compilador señala un error en la línea 25;
- líneas 23-24: llamadas al método [inspect] con tipos derivados de [Number];
Se obtienen los siguientes resultados:
Nota: para ejecutar la clase en Intellij, siga estos pasos [1, 2]:
![]() |
Ahora, consideremos el siguiente ejemplo:
public void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 3; i++) {
list.add(i);
}
}
El método [addNumbers] admite como parámetro un tipo List<T>, donde T es una clase padre de la clase [Integer]. Debido a esta restricción, los enteros int del 1 al 3 pueden añadirse a la lista (líneas 2-4) y el método podría invocarse de la siguiente manera:
List<Number> numbers=new ArrayList<>();
// añadido doble Número <-- Doble
numbers.add(7.8);
// adición Long Número <-- Long
numbers.add(1L);
// adición de List<Number> Number <-- Integer
addNumbers(numbers);
// visualización de List<Number>
for(Number number : numbers){
System.out.println(number.getClass().getName());
}
Se obtiene entonces el siguiente resultado:
Para extender la clase Box<T>, se escribirá:
class OtherBox<T> extends Box<T> {
}
La clase hija puede introducir a su vez nuevos parámetros genéricos:
class AnotherBox<T, T1> extends Box<T> {
void consumer(T1 t1) {
System.out.printf("%s%n", t1);
}
}
Una interfaz también puede utilizar parámetros genéricos:
interface I<T> {
void doSomethingWith(T t);
}
Para implementarla, se utiliza la siguiente sintaxis:
class A<T> implements I<T> {
@Override
public void doSomethingWith(T t) {
System.out.printf("%s%n", t);
}
}
Ahora sabemos lo suficiente sobre genéricos como para abordar las funciones lambda.


