3. As assinaturas das classes e métodos genéricos
![]() |
A biblioteca RxJava possui vários métodos que aceitam como parâmetros instâncias de interfaces genéricas. Por vezes, a assinatura destas é complexa. Eis alguns exemplos:
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)
Estes dois métodos utilizam dois tipos genéricos, T e R. Mas o que significa, por exemplo, a definição do parâmetro do método map: Func1<? super T,? extends R> func ?
As informações sobre genéricos podem ser encontradas em URL [https://docs.oracle.com/javase/tutorial/java/generics/]. Algumas das informações abaixo têm origem nesta URL. Os genéricos surgiram com a versão 1.5 do Java.
Imaginemos um serviço web que fornece informações de diferentes tipos. Por vezes, o serviço web pode não conseguir fornecer essas informações e, nesse caso, tem de sinalizar um erro ao seu cliente. É então possível padronizar a resposta do serviço web da seguinte forma:
public class Response<T> {
// ----------------- propriedades
// estado da operação
private int status;
// eventuais mensagens de erro
private List<String> messages;
// o corpo da resposta
private T body;
...
}
- linha 1: a classe [Response] é parametrizada pelo tipo T;
- linha 9: o tipo T é o tipo do corpo da resposta, que é o que o cliente realmente espera;
- linha 5: um código de erro, 0 se não houver erro;
- linha 7: mensagens que explicam o erro, null se não houver erro;
Do lado do cliente, poderá então escrever-se:
Response<Product> product=getDataFromWebService(...) ;
ou
Response<List<Product>>=getDataFromWebService(...) ;
O tipo formal T é substituído por um tipo efetivo, neste caso [Product] ou [List<Product>]. A classe genérica [Response<T>] revela-se aqui prática, pois permite trabalhar com uma resposta padrão.
Para criar um tipo [Response] com o operador new, escrever-se-á:
Response<List<Product>> response=new Response<List<Product>> (...) ;
A partir da versão 1.8 do Java, a instrução anterior pode ser escrita de forma mais simples:
Response<List<Product>> response=new Response<> (...) ;
Já não é necessário especificar, à direita do sinal =, o tipo efetivo dos parâmetros genéricos: será utilizado o tipo formal especificado à esquerda do sinal =. A isto chama-se inferência de tipo: o compilador é capaz de determinar por si próprio o tipo efetivo dos parâmetros genéricos com base no contexto. Esta característica é amplamente utilizada nas funções lambda, onde o tipo efetivo dos parâmetros genéricos de um método é frequentemente omitido.
Agora, do lado do cliente, o método [getDataFromWebService] poderia ter a seguinte assinatura:
<T1,T2> Response<T1> getDataFromWebService(String urlWebService, String httpMethod, T2 post)
Temos aqui um método genérico parametrizado por dois tipos, T1 e T2:
- T1 é o tipo do corpo da resposta esperada;
- T2 é o tipo do valor enviado para uma operação POST, e null para uma operação GET;
- urlWebService: é o URL do serviço web;
- httpMethod: é o método HTTP, GET ou POST a utilizar para consultar este URL;
Do lado do cliente, poderá ser feita a seguinte chamada:
Long id=... ;
Response<Product> product=this.<Product,Long>getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
Neste caso, teremos T1=Product e T2=Long.
Com o Java 8, é possível utilizar a inferência de tipos e escrever de forma mais simples:
Long id=... ;
Response<Product> product=getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;
Eis outra forma possível de chamar a função:
Long[] ids=... ;
Response<List<Product>> product=getDataFromWebService('http://localhost:8080/rest/products','POST',ids) ;
Neste caso, teremos T1 = List<Product> e T2 = Long[].
A classe ou o método genérico podem impor restrições aos seus parâmetros genéricos. Voltemos à definição do método [map] de RxJava:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
O método aceita dois tipos de parâmetros, T e R. O tipo [Func1] é uma interface genérica:
![]() |
Func1 define uma interface funcional, ou seja, uma interface com apenas um único método. As interfaces funcionais estão na base das funções lambda do Java 8. Aqui, o método da interface é definido como:
Assim, T é o tipo do parâmetro passado para [call] e R é o tipo do resultado obtido. Voltemos à definição do método [map]:
public final <R> Observable<R> map(Func1<? super T,? extends R> func)
- o método [map] espera um único parâmetro do tipo [Func1<? super T,? extends R>] e devolve um tipo [Observable<R>];
- ? super T: o parâmetro passado ao método [Func1.call] deve ser do tipo T ou um tipo pai de T;
- ? extends R: o tipo do resultado devolvido pelo método [call] deve ser do tipo R ou derivado, se R for uma classe, ou implementar R, se R for uma interface;
Para compreender melhor as duas restrições anteriores, vejamos alguns exemplos retirados da referência [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("algum texto"); // erro: isto continua a ser uma String!
}
}
- linha 3: a classe [Box] é parametrizada pelo tipo T, que é o tipo do campo da linha 5;
- linha 15: o método [inspect] aceita um parâmetro do tipo U. Um método também pode ter parâmetros genéricos. Nesse caso, é declarado da seguinte forma:
Aqui, U é declarado como tipo genérico do método por <U>, mas com uma restrição <U extends Number>, ou seja, U deve estender a classe [Number]. Devido a esta restrição, o compilador sinaliza um erro na linha 25;
- linhas 23-24: chamadas ao método [inspect] com tipos derivados de [Number];
Obtêm-se os seguintes resultados:
Nota: para executar a classe no IntelliJ, proceda da seguinte forma [1, 2]:
![]() |
Agora, consideremos o seguinte exemplo:
public void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 3; i++) {
list.add(i);
}
}
O método [addNumbers] aceita como parâmetro um tipo List<T>, em que T é uma classe pai da classe [Integer]. Devido a esta restrição, os inteiros int de 1 a 3 podem ser adicionados à lista (linhas 2-4) e o método poderia ser chamado da seguinte forma:
List<Number> numbers=new ArrayList<>();
// adição de um número duplo <-- Double
numbers.add(7.8);
// adição de um Long a um Number <-- Long
numbers.add(1L);
// adição de um número a uma Lista<Número> <-- Integer
addNumbers(numbers);
// exibição de uma Lista<Number>
for(Number number : numbers){
System.out.println(number.getClass().getName());
}
Obtém-se então o seguinte resultado:
Para estender a classe Box<T>, escrever-se-á:
class OtherBox<T> extends Box<T> {
}
A classe filha pode, por sua vez, introduzir novos parâmetros genéricos:
class AnotherBox<T, T1> extends Box<T> {
void consumer(T1 t1) {
System.out.printf("%s%n", t1);
}
}
Uma interface também pode utilizar parâmetros genéricos:
interface I<T> {
void doSomethingWith(T t);
}
Para a implementar, utiliza-se a seguinte sintaxe:
class A<T> implements I<T> {
@Override
public void doSomethingWith(T t) {
System.out.printf("%s%n", t);
}
}
Já sabemos o suficiente sobre genéricos para abordarmos as funções lambda.


