Artigo Java Magazine 16 - Genéricos no J2SE 5.0

As novidades da linguagem em tipos genéricos. Neste artigo, tentarei explorar as características e conseqüências mais marcantes do novo paradigma.

Esse artigo faz parte da revista Java Magazine edição 16. Clique aqui para ler todos os artigos desta edição

Clique aqui para ler esse artigo em PDF.

Genéricos no J2SE 5.0

As novidades da linguagem em tipos genéricos etc.

  De todas as novidades que já cobrimos do J2SE 5.0 (“Tiger”), o recurso de tipos genéricos é, sem dúvida, o mais significativo – em várias medidas. É o mais esperado (há anos em gestação) e o de maior impacto na linguagem. E é talvez o item que nos tomará mais tempo para assimilar completamente. Estamos acostumados com uma linguagem quase “congelada” desde 1995 e, enquanto vários outros recursos sintáticos do Tiger (como o for estendido ou o static import) são extremamente simples e não tomarão mais de alguns minutos de aprendizado, os tipos genéricos requerem uma atenção séria.

  Tipos genéricos já foram apresentados no artigo “Generics no Java 1.5” (na Edição 3, quando o recurso ainda estava em fase bastante preliminar) e mencionados em outros artigos meus sobre o Tiger. Tutoriais e introduções já estão se tornando comuns, mas a simplicidade aparente do recurso – até a especificação formal da JSR-14 (“Add[ing] Generic Types to the Java Programming Language”) é surpreendentemente legível – pode ser enganosa.

  Não que os tipos genéricos sejam tão difíceis; é que não se trata apenas de um novo conjunto de sintaxes e funcionalidades, mas sim de uma extensão do paradigma de programação orientada a objetos de Java. Dessa forma, a dificuldade de aprendizado é mais uma questão de se habituar a novos conceitos até que estes se tornem “intuitivos”. Neste artigo, tentarei explorar as características e conseqüências mais marcantes do novo paradigma.

Objetivos e Alô Mundo

  Para recapitular um pouco da teoria de linguagens de programação, consulte o quadro “Tipos e Sistemas de Tipos”, que define conceitos relevantes para este artigo. Partindo daí, começamos justificando a necessidade dos tipos genéricos.

Observe o trecho de código a seguir:

Map prodMap = new HashMap();
List frutas = new ArrayList();

frutas.add(new Produto(“Banana”));
prodMap.put(“Frutaria”, frutas);
Produto banana = (Produto)((List)prodMap.get(“Frutaria”)).get(0);
prodMap.put(new Integer(0), “Erro”); // Ok!

  Este código manipula uma estrutura de dados que indexa listas de produtos pelo nome do fornecedor. A versão com tipos genéricos ficaria assim:

Map<String,List<Produto>> produtos =

   new HashMap<String,ArrayList<Produto>>();

List<Produto> frutas =

  new ArrayList<Produto>();

frutas.add(new Produto(“Banana”));
Produto banana = produtos.get(f).get(0);

// Erro de compilação!
prodMap.put(new Integer(0), “Erro”);

  Na versão genérica, declaramos os tipos dos objetos que podem ser colocados nas coleções. Por exemplo, a interface java.util.Map é agora declarada como Map, onde K é o tipo da chave (key), e V, o tipo do valor (K e V são chamados parâmetros de tipo). Já java.util.List é declarada como List, onde E é o tipo dos elementos da lista.

  Ao declarar variáveis, passamos um tipo como argumento para cada um desses parâmetros, como Map<K?String,V?List<E?Produto>>. Note que tais parâmetros, ao contrário dos argumentos passados para métodos, são “passados” estaticamente (durante a compilação); em tempo de execução não haverá nenhum objeto java.lang.Class sendo passado.

No exemplo de código genérico anterior, podemos ver algumas vantagens:
• O uso dos objetos genéricos (no caso, coleções) é bem mais simples, pois não precisamos de nenhum typecast para extrair elementos.
• O uso dos objetos genéricos também é mais seguro: o compilador não permite colocar no mapa elementos incompatíveis com os tipos declarados para a chave e o valor.

  Por outro lado, as declarações e a instanciação ficam mais complexas, pois precisamos acrescentar expressões <...> com os parâmetros de tipo. Mas como uma variável é declarada uma só vez e é usada muitas vezes, o balanço da facilidade de uso costuma ser bastante positivo. Além disso, há muitas situações onde as declarações não são necessárias – por exemplo quando um valor retornado por uma invocação é usado diretamente como parte de outra expressão ou invocação.

  A nova sintaxe de for estendido também coopera muito no caso importante das coleções. Veja os exemplos a seguir (que imprimem todos os produtos de todos os fornecedores):

// (1) Sem código genérico (J2SE 1.4)
for (Iterator i = prodMap.values().iterator(); i.hasNext(); ) {
  ArrayList prods = (ArrayList)i.next();
  for (Iterator j = prods.iterator();  j.hasNext(); )
    System.out.println(((Produto)j.next()).getNome());"

[...] continue lendo...

Artigos relacionados