Whats new? | Login | Parceiros
Cadastre-se | Atendimento | RSS
+ Java:
artigos   |   vídeos   |    cursos   |    mais

Um passeio pelo Java 5 - Generics - Parte I

No artigo passado falei sobre: laço for melhorado, Autoboxing/Unboxing, enumerações (Enums), métodos com número de argumentos variável (Varargs) e Static Import. Nesta seqüência...

CHRISTIAN CLEBER MASDEVAL BRAZ
Christian Cleber Masdeval Braz (masdeval@yahoo.com.br) é bacharel em Ciência da Computação pela UFMS. Em 2004 concluiu o mestrado, também naquela instituição, na área de inteligência artificial e problemas combinatoriais difíceis. Funcionário da Dat...


Ver space do autor


Estatísticas:
Visualizações:
16324
Favoritado:
 9 vez(es)
Conteúdo:
Didática:
Utilidade:
0 2
votos: 5

Serviços:



Um passeio pelo Java 5 - Generics - Parte I

 

Introdução

A última versão mais importante da linguagem Java havia sido a 1.2. De lá para cá novas versões apenas revisaram algumas características e corrigiram bugs. A versão 1.5 (Java 5 como vem sendo chamada), no entanto, é outro marco! Novas características foram incluídas na linguagem tornando-a mais robusta e fácil de usar. A JVM também sofreu melhorias e está mais rápida e confiável.

 

No artigo passado falei sobre: laço for melhorado, Autoboxing/Unboxing, enumerações (Enums), métodos com número de argumentos variável (Varargs) e Static Import. Nesta seqüência, apresento a mais significativa das mudanças ocorridas, que é a possibilidade de se criar classes e métodos parametrizados (semelhante às templates do C++). Uma classe ou método paramétrico está apto a ser invocado com tipos diferentes. Sendo assim, é possível definir uma variável dentro de uma classe ou o argumento de um método como um tipo paramétrico e apenas quando estes forem efetivamente utilizados este tipo será definido pelo usuário. Esta é uma tremenda flexibilidade para codificação e possibilita criar soluções mais genéricas e resumir muito código, o que significa menos necessidade de manutenção. Toda a API padrão da linguagem (todas as classes que implementam coleções por exemplo) foi refeita para tirar proveito destas facilidades.

 

O conceito de classe genérica (generics) é simples porém os detalhes para sua correta utilização são muitos. Espero ao final desse texto ter conseguido explicar de forma clara seus principais pontos.

 

Classes Genéricas

Considere o código abaixo:

 

public interface List

{

void add(E x);

Iterator iterator();

}

public interface Iterator

{

E next();

boolean hasNext();

}

 

As duas interfaces são definidas com o auxílio de parâmetros de tipos formais (formal type parameters), evidenciados pelas letras embutidas entre os sinais <>. Tipos paramétricos podem ser usados no código genérico de forma muito semelhante aos tipos convencionais. Um exemplo de utilização do código acima pode ser:

 

List myList = new LinkedList();

myList.add(new Integer(1));

Integer x = myList.iterator().next();

 

Até o Java 4 existiam apenas listas de Objects e para recuperar qualquer coisa diferente disso era necessário fazer um type cast. Agora pode-se definir a priori qual o tipo dos dados que uma lista (ou qualquer outra coleção) irá armazenar. Uma lista de inteiros receberá apenas inteiros, nada além disso. As conseqüências disso são várias:

·         Não é preciso fazer cast para recuperar elementos já que o tipo correto de retorno já está especificado.

·         O código fica mais robusto pois agora o compilador pode checar os tipos envolvidos (type safe) o que não acontecia quando um cast era necessário.

·         Menos erros em tempo de execução.

·         Exibe naturalmente um certo grau de reaproveitamento de código já que códigos genéricos são feitos para serem utilizados com tipos diferentes de dados.

 

Um ponto muito importante sobre generics está relacionado à subtipos. Considere o exemplo:

 

List ls = new ArrayList();

List lo = ls;

 

O que você acha do código acima? Já que String deriva de Object parece razoável supor que uma lista de strings também é um subtipo de uma lista de objetcs e sendo assim a atribuição acima estaria correta. Pois bem, considere esta continuação

 

lo.add(new Object());

String s = ls.get(0);

 

Na primeira linha um Object foi inserido na lista e na segunda este foi recuperado e atribuído a uma String, o que é um erro! Quando fizemos a variável lo receber ls esta deixou de ser uma lista apenas de strings e abriu a possibilidade de inserirmos qualquer tipo de objeto. Definitivamente este não é o tipo de comportamento que esperamos! É claro que atribuições como a acima não são permitidas e o compilador irá emitir uma mensagem de erro quando encontrá-las.

 

Dica 1. 

passeiojavafig01.JPG

Se Circle é uma subclasse (ou subinterface)
de Shape e G é uma declaração genérica

qualquer então não é verdade que G é

também um subtipo de G. Esta é uma

noção inicialmente difícil de absorver pois vai

contra nossa intuição natural.
 

 

Wildcards

A decisão acima é muito restritiva. Considere a necessidade de imprimir o conteúdo de uma coleção arbitrária e a tentativa de implementá-la exposta no método abaixo

 

void printCollection(Collection c)

{

for(Object e : c)

{

System.out.println(e);

}

}

 

Como vimos, ao contrário da intuição inicial, esse não é um método apto a receber qualquer tipo de coleção, mas sim exclusivamente coleções que contêm objetos do tipo Object. Para representar uma coleção de qualquer coisa devemos usar o caractere ? da seguinte forma: Collection. Isto é chamado de wildcardtype. O novo código utilizando wildcard é

 

void printCollection(Collection c)

{

for(Object e : c)

{

System.out.println(e);

}

}

 

o qual pode ser chamado com qualquer tipo de coleção. Observe que o conteúdo da coleção c é atribuído a um Object. O wildcard é um tipo desconhecido, mas como temos certeza que será um objeto é seguro atribuí-lo a uma variável do tipo Object.

 

Considere uma aplicação gráfica que desenha superfícies (shapes) tais como retângulos e círculos. Para representar esses componentes no programa podemos definir a seguinte hierarquia de classes.

 

passeiojavafig02.JPG 

 

Um método conveniente seria um para desenhar uma coleção de superfícies tal como

 

public void drawAll(List shapes)

{

for(Shape s: shapes)

{

s.draw(this);

}

}

 

O problema aqui (novamente) é que esse método só pode ser invocado recebendo como parâmetro uma List. Um List por exemplo causaria erro de compilação. Isso é muito ruim tendo em vista que o objetivo do método é ler uma coleção de superfícies, sejam elas círculos ou retângulos. O que precisamos é de um método que aceite uma lista de qualquer tipo de superfície:

 

public void drawAll(Listextends Shape> shapes)

{

for(Shape s: shapes)

{

s.draw(this);

}

}

 

Listextends Shape> é um exemplo de bounded wildcard. O ? representa, como antes, um tipo desconhecido. Entretanto, neste caso, sabemos que esse tipo é um Shape ou um subtipo deste. A desvantagem de não ser possível modificar uma variável com tipo desconhecido permanece.

 

Métodos Genéricos

Declarações de métodos também podem ser genéricas, isto é, parametrizadas por um ou mais parâmetro de tipo. Imagine como fazer um método que recebe um array de objetos e uma coleção e deve acrescentar todos os objetos do array dentro da coleção. Uma tentativa pode ser

 

public void fromArrayToCollection(Object[] a, Collection c)

{

for(Object o : a)

{

c.add(o); //Erro de compilação!

}

}

 

A opção de fazer Collection para ser possível receber uma coleção arbitrária foi correta, porém, tornou a coleção c apenas para leitura. Se reformularmos este método como um método genérico podemos resolver esse problema:

 

public void fromArrayToCollection(T[] a, Collection c)

{

for(T o : a)

{

c.add(o);

}

}

 

Podemos chamar este método com qualquer tipo de coleção cujos elementos sejam um supertipo dos elementos do array. Por exemplo:

 

Object[] objectArray = new Object[100];

Collection objectCollection = new ArrayList();

//Suponha as seguintes invocações do método fromArrayToCollection

fromArrayToCollection( Object, Object ); //Infere ser Object

fromArrayToCollection( String, Object ); //Infere ser Object

fromArrayToCollection( Integer, Number ); //Infere ser Number

fromArrayToCollection( Number, Object ); //Infere ser Object

fromArrayToCollection( Number, String ); //Erro de compilação!

 

Nas quatro primeiras chamadas do método o compilador pôde inferir um tipo válido para T, isto é, um tipo que é supertipo do outro. Perceba que não é necessário especificar explicitamente um tipo para um método genérico. O compilador irá inferir o tipo apropriado baseado nos tipos atuais. Na última chamada o método recebe como argumento um array de números e uma coleção de strings. Como String não é um supertipo de Number (e vice-versa) o compilador não consegue concluir um tipo válido e acusa erro.





Participe! Inclua um comentário
[Fechar]

Este post é fechado - você precisa ter acesso ao post para incluir um comentário.


Nenhum comentário foi postado - seja o primeiro a comentar ;-)



 


[Fechar]
Este post está disponível para assinantes da Java Magazine ou para quem possui Créditos DevMedia.

  Conheça os planos de créditos DevMedia e visualize esse post agora mesmo!

Plano conveniência – Neste plano este post custa R$ 0,00 (Compre agora)
Esse plano permite que você compre somente um post, pagando por ele seu preço sem desconto.

Plano ocasional: Aqui este post custa: R$ -1,00 (assinante) ou R$ -1,00 (não-assinante)
Este plano é ideal para quem tem interesse em mais de um post. Você compra um mínimo de R$ 50,00 em créditos e ganha, em média, 50% de desconto no preço do post. Compre Créditos agora!

Assinatura de Créditos (Plano econômico) – Aqui este post custa R$ -1,00
Este plano é ideal para quem tem interesse em muitos posts. Com esse plano você compra R$ 180,00 em créditos e ganha, em média, 80% de desconto no preço do post. Assine este plano agora!

> Saiba mais sobre o Sistema de Créditos DevMedia
DevMedia Group   www.devmedia.com.br   |   www.javafree.org   |   www.mrbool.com
2010 - Todos os Direitos Reservados a DevMedia Group - (21) 3382-5038