Usando Generics em Java

Veja neste artigo os principais usos e especificidades de Generics e veja também como utilizar os Generics em Java.

A motivação de estudar Generics em Java é de poupar o desenvolvedor de códigos redundantes, como é o caso de casting excessivo. Este foi introduzido desde o Java SE 5.0. Vamos neste artigo abordar os principais usos e especificidades de Generics, para que você leitor possa entender o funcionamento do mesmo e utilizá-lo em seus projetos com maior frequência e facilidade.

Para iniciarmos vamos a um exemplo muito comum, apresentado na listagem 1, que mostra como ficaria uma Lista de Objetos com Generics e outra sem Generics.

/* COM GENERICS */ List<Apple> box = ...; Apple apple = box.get(0); /* SEM GENERICS */ List box = ...; /* Se o objeto retornado de box.get(0) não puder ser convertido para Apple, só saberemos disso em tempo de execução */ Apple apple = (Apple) box.get(0);
Listagem 1. Uso do Generics

De inicio já podemos notar 2 problemas básicos que são encontrados quando optamos por não utilizar Generics:

List<Orange> box = ...; /* Erro em tempo de compilação pois uma lista de Orange não pode ser atribuido a um objeto do tipo Apple. Isso porque ao fazer “box.get(0)” estamos retornando um Orange e não um Apple. */ Apple apple = box.get(0);
Listagem 2. Erro em tempo de compilação com Generic

Generic em Classes e Interfaces

Podemos também utilizar os Generics em Classes ou Interfaces. Estes servem como parâmetro para nossa Classe, assim poderemos utilizar esta “variável” em todo escopo de nossa classe. Veja o exemplo de utilização da listagem 3.

public interface List<T> extends Collection<T> { ... }
Listagem 3. Usando Generic em Interfaces

Esta é a interface da List em Java, perceba que podemos fazer: List pois a interface nos permite isso. A vantagem de fazer isso é o retorno do Objeto quando fazemos um “get”, veja na listagem 4.

T get(int index);
Listagem 4. Retornando objetos generics

O método acima irá retornar um objeto do tipo “T” dado determinado index, e quem é T ? Em princípio não sabemos, só vamos descobrir ao implementar a interface. Na listagem 1 o nosso T = Apple.

Sendo assim, o mesmo poderá ser utilizado durante todo desenvolvimento da interface para evitar o uso de castings excessivos. São inúmeras as possibilidades que temos ao se trabalhar com Generics em Classes e Interfaces, muitos problemas que antes seriam resolvidos com horas e até dias de código “sujo”, podem ser resolvidos em apenas algumas linhas, basta ter a habilidade necessária para utilizar tal ferramenta.

Generics em Métodos e Construtores

Antes de começar a explicação, vamos mostrar o exemplo do uso de Generics em um método Java.

public static <T> getFirst(List<T> list)
Listagem 5. Usando Generics em Métodos

Perceba na listagem 5 que o método getFirst aceita uma lista do tipo T e retorna um objeto do tipo T.

A iteração em Java (Iterator) também possui Generic, assim um Iterator pode facilmente ser retornado para um novo objeto sem a necessidade do Cast explícito. Veja o exemplo na listagem 6.

for (Iterator<String> iter = str.iterator(); iter.hasNext();) { String s = iter.next(); System.out.print(s);
Listagem 6. Usando Generic no Iterator

O que você percebe acima é o uso do Generic para transformar o Iterator em iterações apenas de String, assim sempre que fizermos “iter.next()” estamos automaticamente retornando uma String sem precisar fazer “String.valueOf(iter.next())”.

O código da listagem 6 também pode ser convertido para um foreach, assim aproveitamos também o recurso de generic que nós é oferecido.

for (String s: str) { System.out.print(s); }
Listagem 7. Convertendo Listagem 6 para foreach

Subtipos Genéricos

Antes de iniciar a explicação do funcionamento de subtipos em Generics, é importante entender o conceito em Java. Entenda que em Java a seguinte hierarquia é totalmente possível:

Figura 1. Hierarquia de Subtipos

Significa que um Apple é um Fruit e um Fruit é um Object, então um Apple é um Object também. Analogamente, se A é filho B e B é filho de C, então A é filho de C. A referencia contida na listagem 8 só é possível por esta hierarquia imposta no Java.

Apple a = ...; Fruit f = a;
Listagem 8. Hierarquia de classes

Tudo isso foi explicado para que possamos finalmente chegar neste assunto incluso no Generics, veja o código abaixo:

List<Apple> apples = ...; List<Fruit> fruits = apples;
Listagem 9. Subtipos em Generics

Se seguirmos o raciocínio da explicação acima, o código acima será compilado normalmente sem nenhum erro. Porém isso não é possível no Generics, pois em algumas situações poderia ocorrer uma quebra a consistência da linguagem. Como assim ? Vamos explicar.

Imagine que você tem o código da listagem 9 funcionando normalmente (o que não é o caso). Temos então uma caixa de maças, e transformamos nossa caixa de maças em uma caixa de frutas, pois toda maça é fruta, certo ?

Agora já que temos uma caixa de frutas (que sabemos na verdade que ela é uma caixa de maças), o que nos impede de colocar morangos ? Nada. Veja na listagem 10 o que poderíamos fazer se a listagem 9 compilasse sem erros.

List<Apple> apples = ...; List<Fruit> fruits = apples; fruits.add(new Strawberry());
Listagem 10. Forma errada de usar Generics

O código acima está logicamente errado, pois antes havíamos atributo a caixa de maças a caixa de frutas, ou seja, nossa atual caixa de frutas na verdade é uma caixa de maças, mas desta forma nada nos impede de colocar um moranga em uma caixa de frutas, isso porque, um morango também é uma fruta. É por este motivo que o Generic não permite esse tipo de atribuição. Podemos resumir isso tudo a apenas uma só frase: Generics são invariantes.

Wildcards

Antes de começar a entender o funcionamento de WildCards em Generics, entenda o porque utilizá-lo. Lembre-se da listagem 10 onde mostramos que não é possível atribuir uma lista de “Apples” a uma lista de “Fruits” mesmo o tipo Apple sendo filho de Fruit, isso porque estaríamos quebrando o “contrato” que diz que Apple só aceita Apple, pois se colocarmos este como Fruit, poderíamos aceitar Morangos, laranjas e etc.

Enfim, com Wildcards solucionamos este problema. Veja como:

Existem 3 tipos de Wildcards em Generics:

Vamos explicar os 3 nos próximos tópicos.

Unknown Wildcard

Como você não sabe o tipo do objeto, você deve tratá-lo da forma mais genérica possível. Veja o exemplo abaixo do uso deste Wildcard.

public void processElements(List<?> elements){ for(Object o : elements){ System.out.println(o); } } /* Podemos atribuir um list de qualquer tipo a nosso método, pois ele tem um tipo desconhecido/genérico */ List<A> listA = new ArrayList<A>(); processElements(listA);
Listagem 11. Usando Unknown Wildcard

Extends Wildcard

Podemos utilizar este tipo de Wildcard para possibilitar o uso de vários tipos que se relacionam entre si, ou seja, podemos dizer que o nosso método processElements aceita uma lista de qualquer tipo de Frutas, seja moranga, maça ou etc. Vejamos.

public void processElements(List<? extends Fruit> elements){ for(Fruit a : elements){ System.out.println(a.getValue()); } } /* Podemos agorar passar nossas frutas diversas ao método processElements */ List<Apple> listApple = new ArrayList<Apple>(); processElements(listApple); List<Orange> listOrange = new ArrayList<Orange>(); processElements(listOrange); List<Strawberry> listStrawberry = new ArrayList<Strawberry>(); processElements(listStrawberry);
Listagem 12. Usando extends wildcard

Super wildcard

Ao contrário do extends, o wildcard super permite que elementos Fruit e Object sejam utilizados, isso significa que apenas são permitidos de “Fruit para cima”. Se fizermos um List estamos permitindo todos os Apples, Fruits e Objects.

Para finalizar, vamos a um último exemplo utilizando Generics e vendo qual será a saída do mesmo:

public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("Hello World")); System.out.printf("Integer Value :%d\n\n", integerBox.get()); System.out.printf("String Value :%s\n", stringBox.get()); } }
Listagem 13. Último exemplo Generics

Saída deste código:

Integer Value :10 String Value :Hello World
Listagem 14. Saída da Listagem 13

Conclusão

O uso de Generics faz-se necessário para evitar casts excessivos e erros que podem ser encontrados em tempo de compilação, antes mesmo de ir para a produção. Todo profissional da área deve ter o conhecimento de como utilizar este recurso tão poderoso, pois em muito se aumenta a produtividade utilizando-o.

Confira também

Artigos relacionados