Saltar para o conteúdo

Utilitários: Coleções aumentadas

12 de Maio de 2009

A plataforma java oferece uma vasto suporte ao conceito de conjuntos (coleções e mapas), mas não tão vasto quanto poderia ser.

A limitação à API de coleções atual é o uso da classe Colections para coisas avanaçadas como ordenação e a falta de suporte a Closures pelo Java. Mesmo se o Java tivesse suporte, depois das discussões para o Java 7 – a closures não ha garantia de que a API de coleções seria alterada.

Mesmo com o suporte a coleções da plataforma falta suporte a outras estruturas – que não sendo coleções – estão relacionadas a teoria de conjuntos como intervalos.

Intervalo

O MiddleHeaven dá suporta a intervalos pela classe Interval definida conjunto de elementos ordenável. Elemento ordenável é todo aquele que implements Comparable ou a que se possa associar um Comparator. Um intervalo pode ser fechado ( tem principio e fim) , aberto ( sem principio ou sem fim) ou vazio ( o principio e o fim são iguais).

A classe Interval suporta várias operações que podem ser feitas sobre ou com intervalos, tais como interseção, união ou verificar se um elemento está no intervalo.

Era importante incluir conceito de intervalo. Isso é especialmente relevante o dominio de tempos e datas (Time Toolbox) onde é comum definir intervalos de tempo. O MiddleHeaven define TimeInterval como uma extensão de Interval aplicada a TimePoint e adiciona operações relacionadas a tempo como a conversão para Period

Range

Um intervalo não é iterável porque ele pode ser aberto. Para ter um objeto semelhante a um intervalo mas que é iterável temos o Range. O objeto Range parece-se muito com um intervalo, mas podemos iterar os elementos através de um Incrementator. Um incrementador permite passar de um elemento ao próximo de uma forma controlada. Por exemplo, para passar de 1 a 2 adicionamos 1, mas para passar de 2009-09-10 a 2009-09-11 temos que obter a proxima data. Não podemos simplesmente adicionar 1.

O incrementador é especialmente relevante para elementos de um conjunto ordenável e denso como os numeros reais, já que para estes tipos de conjunto não podemos determinar qual é o proximo elemento a partir de um elemento dado. Por exemplo, nos numeros reais, a seguir a 1 existe um numero, mas não é 2, nem 1.5, nem 1.1, nem 1.0000001 , nem … Por causa desta impossibilidade matemática, ao criar um Range sobre um conjunto denso é necessário estabelecer o passo através de um incrementador. Por exemplo de 1 em 1, poderiamos iterar de 2 a 5 e teriamos [2, 3 ,4, 5] , mas como passo de 0.5 teriamos [2, 2.5 , 3 , 3.5 , 4 , 4.5 , 5].

Caminhando sobre o conjunto

A API de collections é muito boa usando o padrão Iterator ( um dos padrões GoF) mas falha usando o padrão Visitor (também do GoF) . O padrão visitor é muito util para fazer passar um objeto por todos os elementos da coleção. Assim, a coleção recebe um objeto que visita todos os elementos para um determinado fim. O padrão visitor é muito util porque esconde a iteração do for ou do while e desta maneira deixa a coleção fazer a caminhada pelos elementos como ela quiser ( em tese da forma mais eficiente). O padrão Visitor é aquele que seria utilzado por closures, caso elas existissem para aplicar uma certa logica a cada elementos do conjunto. O MiddleHeaven suporta o conceito por detrás do padrão visitor e introduz as interfaces Walkable e Walker. Um Walkable é um objeto que pode ser “caminhado” por um “caminhante” (Walker) em analogia a que um objeto Iterable pode ser iterado através de um Iterator.

A interface Walker apenas define um método genérico doWith() que pode ser usado para qualquer coisa. Embora o método não defina um retorno explicito, é possivel implementar uma outra classe que implemente esta interface e permita o acesso a um resultado através de outro objeto. Esse é o caso, por exemplo, de NumberAcumulator que extend Acumulator e implementa Walker e permite somar valores continos na coleção.

1
2 NumberAcumulator<Real> acumulator = NumberAcumulator.instance () ;
3
4 Range range = Range.over ( Real.valueOf ( 1 ) , Real.valueOf ( 6 ) ,Real.ONE ()) ;
5 // calcula a soma de 1 até 6
6 range.each ( acumulator ) ;
7
8 assertEquals ( Real.valueOf ( 21 ) , acumulator.getResult ()) ;
9

Código 1: Exemplo de uso de acumulador

Aqui utilizámos a classe Real porque NumberAcumulator precisa de um objeto que implemente a estrutura matemática GroupAditive. Real implementa uma forma especial de criar Range O código ficaria assim:

1
2 NumberAcumulator<Real> acumulator = NumberAcumulator.instance () ;
3
4 // calcula a soma de 1 até 6
5 Real.ONE () .upTo ( 6 ) .each ( acumulator ) ;
6
7 assertEquals ( Real.valueOf ( 21 ) , acumulator.getResult ()) ;
8
9

Código 2: Simplificação para criar um Range

Imprimir os numeros de 1 a 6 mostra um uso mais tradicional do Walker

01
02 Range.over ( 1 , 6 ) .each ( new Walker (){
03
04 public void doWith ( Integer it ){
05
06 System.out.println ( it ) ;
07
08 }
09
10 }) ;
11

Código 3: Usando Walker

Em java puro não teria muito ganho porque este Walker é muito simples. Mas se o Walker executar operações complexas (mais de 5 linhas) escrever isso dentro de um for é confuso. colocando isso em uma objeto é possivel definir a logia à parte e de forma genérica. Este é o objetivo, entre outros, de trazer closures para Java. Segundo as tendencias qualquer interface poderia ser substituida por uma closure. Embora closures não venham na versão 7 do Java é quase certo que virão em algum ponto. Enquanto não, as classe anónimas internas terão que servir.O ganho real viria da simplificação da sintaxe com o uso de Closures. Se um dia elas vierem vc poderia escrever assim:

1
2 Range.over ( 1 , 6 ) .each () { System.out.println ( it ) } ;
3

Código 4: Exemplo de como seria o uso de Closures em futuras versões do Java

Um outro tipo de Walker importante é aquele que caminha sobre uma arvore. Para este tipo de objetos o MiddleHeaven introduz a interface TreeWalkable que extende Walkable com os métodos eachRecursive() e eachParent(). O método eachRecursive aplica o Walker ao objeto, depois aos filhos e depois aos filhos dos filhos, etc.. O método eachParent() é aplicado ao pai do objeto corrente e depois ao desse , assim até à raiz. TreeWalkable é especialmente útil para estruturas como o sistema de arquivos. ManagedFile da Managed File Toolbox implementa esta interface.

Enumerable

A classe utilitária Collections permite utilizar vários métodos sobre objetos que estendam Collection ou Map. Isso é feito para que os mapas e coleções em Java não tenham que implementar um numero elevado de métodos assim diminuindo a sua área de superfície. Isto é considerado uma boa prática. Contudo é estre,amente chato e ineficiente já que cada implementação não pode otimizar o processo definido pelo contrato do método. Além disso a chamada dos métodos é pouco encadeável o que confunde os programadores iniciantes e frustra os experientes.

Um outro problema com os mapas e coleções em java é a fala de uma interface comum para coleções e mapas.

O MiddleHeaven introduz a interface Enumerable (enumerável). Esta interface serve dois propósitos: 1) o de prover uma interface comum para mapas e coleções e extender o numero de métodos directamente invocáveis sobre os conjuntos.Um Enumerable é uma expanção do conceito de Iterator. Todos os Enumerable são Iterator mas têm mais coisas a oferecer.

A maior parte dos métodos de Enumerable recebem implementações de Classifier. Este é um objeto que dado um objeto de um tipo retorna outro objeto do mesmo ou de outro tipo. Em particular pode retornar um objeto do tipo Boolean. A maior parte dos métodos de Enumerable usam classificadores para boolean excepto map que usa classificadores para qualquer outro objeto. O objeto Classifier pode ser usado como filtro ou como transformador. Por exemplo, usando o método find() é possivel encontrar um objeto que passe no teste do classificador passado como argumento. Tudo isto sem ter que escrever nenhuma instrução for.

Enumerable implementa Walkable e todas as operações com Classifier geram novos conjuntos após aplicar o classificador a todos os elementos do ocnjunto. Os métodos que usam Classifier implementam o padrão Visitor de forma semelhante a Walker, a diferença é que o resultado obtido pelo Walker não é convertido para um outro conjunto, enquanto que os que usam Classifier são.

Com isto o MiddleHeaven extende as interfaces Collection,Set, List e Map implementando Enumerable e mais alguns métodos interessantes. Estes conjuntos são chamados de coleções aumentadas (enhanced) e implementam as interfaces EnhancedCollection, EnhancedSet , EnhancedList e EnhancedMap respectivamente. Para os obter basta invocar CollectionUtils.enhance(). O uso deste método é inivitável em Jaa devido ao fato de não ser possivel adicionar este método nas interfaces já existentes. Criar uma coleção aumentada é muito simples:

1
2 List<Integer> lista = Arrays.asList ( 1 , 2 , 3 , 4 , 5 , 6 ) ; // lista na plataforma Java
3
4 EnhancedList<Integer> elista = CollectionUtils.enhance ( lista ) ;
5
6

Código 5: Aumentando (enhancing) uma lista

Enumerable permite enumerar e iterar sobre um conjunto em uma certa ordem. Mas e que tal obter um elemento aleatóriamente de uma lista ou coleção ? O MiddleHeaven inclui a interface RandomEnumerable. Esta interface define o método random() que seleciona um elmento aleatóriamente da coleção. Isso permite o sorteio de numeros em uma linha de codigo. Por exemplo para sortear os numeros de um dado fariamos:

1
2 Integer resultado = Range.over ( 1 , 6 ) .random () ;
3

Código 6: Sorteando um numero de um dado

Porquê

Muitos já tentaram criar bibliotecas alternativas para coleções. A Apache Commons Collections existia mesmo antes da API ser padrão na plataforma Java. Trabalhar com conjuntos é realmente uma facilidade que permite ao mesmo tempo tipagem forte, algoritmos eficientes e simplicidade para trabalhar com muitos objetos simultaneamente. O MiddleHeaven não poderia,portanto, se escusar que dar algum suporte a coleções.

Com o olho no Java 7 e a suposta introdução de closures, as coleções aumentadas teriam um uso mais simples e omnipresente mas mesmo sem a funcionalidade de closures ha muita coisa que é mais fácil de ser feita com o uso destas coleções. Por outro lado, devido à necessidade de suportar intervalos (sobretudo os de tempo) era necessário introduzir classes diferentes mas de forma compatível. Mas um intervalo não é iterável, então foi criado o objeto Range.

Converter qualquer conjunto da plataforma padrão para a biblioteca aumentada é muito simples. Aliás , à exceção de EnhancedArrayList que pode ser usada independente, aumentar uma coleção já existente é a única forma de obter uma coleção aumentada.

Deixe um Comentário

Deixe um comentário