Parte 2 - As interfaces funcionais e as novidades da API Collections
Parte 3 - A classe Stream e conclusão
As interfaces funcionais e as novidades da API Collections
As
novas interfaces funcionais do Java 8
Os
especialistas nas expressões Lambdas adicionaram ao Java 8 um pacote
de interfaces funcionais muito úteis quando focando no modelo de
programação voltado a essas expressões. Com o uso de genéricos e
interfaces funcionais, foram definidas diversas interfaces que podem
ser usadas pelo programador quando criando seu próprio código ou em
designers de APIs. No pacote java.util.function, você
encontra diversas interfaces funcionais, entre elas destamos:
Predicate
É
uma interface funcional para testes booleanos. O método test
é usado para realizar um teste booleano com os atributos de um
objeto passado como parâmetro. É possível utilizar os métodos a
and e ou para
combinar diversos resultados booleanos. A interface é parametrizada
através de generics, sendo
que o parâmetro vai determinar o tipo de objeto a ser recebido no
método test.
Function
Executa
a função sobre um objeto T retornando um objeto do tipo R. Essa
interface contém somente o método apply,
que recebe T para retornar R. É possível usar o método andThen
para configurar uma função a ser aplicada após que aplicamos essa
e o método compose
para configurarmos uma função que será aplicada antes dessa.
Consumer
Simplesmente
aplica uma ação sobre o objeto de tipo T através
do método apply. Similarmente
à interface Function, podemos usar um método andThen para
configurar um Consumer que deve ser chamado após a execução desse.
BinaryOperator
Essa
interface funcional recebe dois objetos do tipo T e retorna um objeto
do tipo T. Podemos utilizar os métodos estáticos maxBy
e minBy para que
tenhamos um implementação dessa função que calcula o máximo e o
mínimo de acordo com o Comparator passado
para esses métodos.
Supplier
Cria
um objeto do tipo T. O método get é
chamado e deve retornar um objeto do tipo T. Importante deixar claro
que a interface funcional em sí não necessariamente obriga quem
está implementando a criar um objeto, mas sim retornar um do tipo T,
ficando a cargo de quem implementa como deveria ser realizada a
criação do objeto.
As
interfaces funcionais são parametrizadas através do uso de
generics, muitas
vezes determinando os parâmetros que as funções recebem. O uso
dessas funções fica mais evidente nos exemplos a seguir, onde
apresentamos o uso de Lambdas na API Collections do Java.
As
novidades da API Collections
As
collections, ou coleções, sofreram mudanças significativas no
decorrer dos anos: foram parametrizadas através de Generics,
ganharam novas classes e uma sintaxe mais agradável para iterarmos
sobre os elementos de uma das classes da API de collections.
No
entanto, uma manipulação dos elementos de uma lista ainda acontece
através da escrita de muito código e pouco intuitiva. Por exemplo,
digamos que temos uma lista de objetos do tipo Produto com os
atributos nome, data de criação, quantidade em estoque e marca. Há
uma aplicação que lista esses produtos. Surge um requerimento para
tratamento de produtos em excesso de uma determinada marca: produtos
que tiverem mais que 5 unidades da marca Super e criados no ano de
2014 devem ser submetidos a um processamento através do método
processaProdutoEmExcesso e
também devemos somar a quantidade de todos produtos que estiverem
nessa condição. Já produtos antigos, fabricados antes de 2000,
devem ser processados usando o método processaProdutoAntigo,
além de informar o mais antigo
produto de todos. Utilizando a maneira convencional, temos o código
na Listagem 7, onde
fazemos uso de loops e verificação manual de cada elemento. Notem
como o código é inflexível e nos leva ao uso de diversos comandos
para atingir um objetivo simples.
Listagem
7. Aplicando determinadas ações sob uma lista
List<Produto>
produtos = todosProdutos();
for
(Produto p : produtos){
if(p.qtdeEstoque
> 5 && p.marca.equals("Super") &&
p.dataCriacao.get(ChronoField.YEAR) == 2014){
processaProdutoEmExcesso(p);
somaProdutosMarcaSuper+=p.qtdeEstoque;
}
if(p.dataCriacao.get(ChronoField.YEAR)
< 2000){
processaProdutoAntigo(p);
if(p.dataCriacao.isBefore(maisAntigo.dataCriacao))
maisAntigo = p;
}
}
Com
o tempo, diversas necessidade de filtragem desse relatório aparecem
e temos que flexibilizar o mesmo. Aí modificamos o método acima
para flexibilizar a aplicação de uma condição e a execução de
uma ação como mostrado na Listagem 8,
onde fazemos uso das interfaces funcionais Predicate
e Consumer, descritas
anteriormente. A chamada desses métodos usando a forma tradicional,
com classes internas é verbosa e pode ser vista na Listagem
9 e a forma melhorada, com
Lambdas, está na Listagem 10.
Listagem
8. Melhorando o código anterior para flexibilizar a condição
de filtragem da lista
public
static void realizaProcessamentoSobCondicao(List<Produto>
produtos, Predicate<Produto> condicao, Consumer<Produto>
acao){
for(Produto
p : produtos){
if(condicao.test(p)){
acao.accept(p);
}
}
}
Listagem
9. Utilizando método realizaProcessamentoSobCondicao usando
Classes Anônimas
realizaProcessamentoSobCondicao(produtos,
new
Predicate<Produto>(){
public
boolean test(Produto p){
return
p.qtdeEstoque > 5 && p.marca.equals("Super") &&
p.dataCriacao.get(ChronoField.YEAR) == 2014;
}
},
new
Consumer<Produto>(){
public
void accept(Produto p){
processaProdutoEmExcesso(p);
somaProdutosMarcaSuper+=p.qtdeEstoque;
}
}
);
realizaProcessamentoSobCondicao(produtos,
new
Predicate<Produto>(){
public
boolean test(Produto p){
return
p.dataCriacao.get(ChronoField.YEAR) < 2000;
}
},
new
Consumer<Produto>(){
public
void accept(Produto p){
processaProdutoAntigo(p);
if(p.dataCriacao.isBefore(maisAntigo.dataCriacao))
maisAntigo = p;
}
}
);
Listagem
10. Utilizando método realizaProcessamentoSobCondicao usando
expressões Lambdas
realizaProcessamentoSobCondicao(produtos,
p
-> p.qtdeEstoque > 5 && p.marca.equals("Super")
&& p.dataCriacao.get(ChronoField.YEAR) == 2014,
p
->{
processaProdutoEmExcesso(p);
somaProdutosMarcaSuper+=p.qtdeEstoque;
}
);
realizaProcessamentoSobCondicao(produtos,
p
-> p.dataCriacao.get(ChronoField.YEAR) < 2000,
p
-> {
processaProdutoAntigo(p);
if(p.dataCriacao.isBefore(maisAntigo.dataCriacao))
maisAntigo = p;
}
);
Nosso
código, no entanto, ainda está muito verboso e falta flexibilidade,
pois a cada programa que criarmos, temos que criar novos métodos
semelhantes ao realizaProcessamentoSobCondicao,
mostrado na Listagem 8.
Não seria interessante já ter algo semelhante a isso disponível na
API nativamente?
A
API Collections da versão 8 do Java traz uma forma interessante de
tratarmos listas fortemente baseado nas expressões Lambdas e com uso
das interfaces funcionais já descritas. O código mostrado acima
poderia ser reescrito em uma linha e ter a leitura do mesmo muito
facilitada. A classe que possibilita o uso extensivo de Lambdas e
potencializa o uso de coleções no Java é a classe Stream.
Até mais na parte 3 onde falamos da classe Stream...
Até mais na parte 3 onde falamos da classe Stream...
Nenhum comentário:
Postar um comentário