quitZAUMMM 18 Denunciar post Postado Fevereiro 24, 2011 Com as closures do Java 8 previstas apenas para meados de 2012, surgem outras possibilidades para trabalhar um pouco mais funcionalmente. Enquanto isso não acontece, como podemos fazer para que os conceitos de programação funcional ajudem a escrever o nosso código? Consideremos o caso de calcular a média ponderada de uma List<Prova>, onde Prova tem nota e peso. Em java teríamos um laço como: public double mediaPonderada(List<Prova> provas) { double somaNotas = 0.0; double somaPesos = 0.0; for (Prova prova : provas) { somaNotas += prova.getNota() * prova.getPeso(); somaPesos += prova.getPeso(); } return somaNotas / somaPesos; } Mas gostaríamos de algo mais genérico, para poder tirar média ponderada de qualquer objeto. A abordagem padrão em java é extrair uma interface com os getters para o valor da nota e para o peso, e fazer com que Prova implemente essa interface: public interface Ponderavel { double getValor(); double getPeso(); } public class Prova implements Ponderavel {...} Assim a implementação do mediaPonderada ficaria: public double mediaPonderada(List<Ponderavel> ponderaveis) { double soma = 0.0; double somaPesos = 0.0; for (Ponderavel ponderavel : ponderaveis) { soma += ponderavel.getValor() * ponderavel.getPeso(); somaPesos += ponderavel.getPeso(); } return soma / somaPesos; } E o que acontece se não pudermos (ou não quisermos) implementar a interface só pra chamar o método mediaPonderada? Teríamos que usar, então, a mesma abordagem que o Collections.sort, e criar uma outra interface para poder calcular a média ponderada de uma lista qualquer: public interface Ponderante<T> { double valorDe(T t); double pesoDe(T t); } assim a chamada do método ficaria: List<Prova> provas = ...; double media = mediaPonderada(provas, new Ponderante<Prova>() { public double valorDe(Prova prova) { return prova.getNota(); } public double pesoDe(Prova prova) { return prova.getPeso(); } }); Isso resolve o problema de uma forma geral, mas prejudica bastante a leitura da chamada do método com a criação da classe anônima. Para quem já programou em Scala ou em alguma outra linguagem funcional, a abordagem natural seria passar os métodos a serem chamados nos objetos da lista como argumentos do método mediaPonderada. Por exemplo, o código em Scala seria: var media = mediaPonderada(provas, _.nota, _.peso) ... Nesse código chamaremos os métodos nota e peso (os equivalentes aos getters de java) em cada elemento da lista provas, usando-os como valor e peso. Mas será que conseguimos fazer algo parecido com isso no java que temos hoje em dia? A resposta é sim, basta deixarmos de lado alguns dos nossos preconceitos e usarmos a criatividade. A primeira coisa que precisamos é receber os métodos que serem chamados como parâmetro do mediaPonderada. Poderíamos usar o java.lang.Method para isso, mas para evitar a grande quantidade de exceptions que deveriam ser tratadas e a programação orientada a string, usaremos a Function do Google Guava: public <T> double mediaPonderada(List<T> lista, Function<T, Double> valor, Function<T, Double> peso) { double soma = 0.0; double somaPesos = 0.0; for (T t : lista) { soma += valor.apply(t) * peso.apply(t); somaPesos += peso.apply(t); } return soma / somaPesos; } A princípio a chamada do método não melhorou muito: double media = mediaPonderada(provas, new Function<Prova, Double>() { public Double apply(Prova p) { return p.getNota(); } }, new Function<Prova, Double>() { public Double apply(Prova p) { return p.getPeso(); } }); Agora precisamos criar uma forma de capturar uma chamada de método e transformá-la numa Function. Para capturar a chamada, podemos usar um proxy, que é uma classe filha de uma interface ou classe em que podemos controlar o seu comportamento através de um MethodInterceptor, que intercepta todas as chamadas de métodos. O VRaptor já possui um criador de proxies pronto, o ObjenesisProxifier, que torna bem simples o processo de criar um proxy. Vamos, então, criar uma classe e um método para poder criar esse proxy facilmente e guardar o método capturado num um pouco deselagante ThreadLocal (para possibilitar uma maior flexibilidade, deveríamos guardar uma pilha de métodos e argumentos): public class Funcional { private static final ThreadLocal<Method> method = new ThreadLocal<Method>(); private static final ThreadLocal<Object[]> args = new ThreadLocal<Object[]>(); public static <T> T of(Class<T> type) { return new ObjenesisProxifier().proxify(type, new MethodInvocation<T>() { public Object intercept(T proxy, Method method, Object[] args, SuperMethod superMethod) { Funcional.method.set(method); Funcional.args.set(args); return null; } }); } } Estamos usando métodos estáticos para poder usar o import static e deixar o código um pouco mais legível, e precisamos do ThreadLocal para podermos armazenar o método chamado sem se preocupar com problemas de concorrência. Agora precisamos de um outro método que transforma o método capturado numa Function: public static <T,F> Function<T,F> function() { final Method method = method.get(); final Object[] args = args.get(); return new Function<T,F>() { public T apply(F f) { return (T) new Mirror().on(f).invoke() .method(method).withArgs(args); } }; } Usamos aqui o Mirror, que permite invocar o método via reflexão envelopando as checked exceptions da API de reflection em exceções de runtime. Assim, para conseguir criar a Function só precisaríamos chamar o método of antes: of(Prova.class).getNota(); Function<Prova, Double> getNota = function(); of(Prova.class).getPeso(); Function<Prova, Double> getPeso = function(); double media = mediaPonderada(provas, getNota, getPeso); E se tirarmos vantagem da ordem de chamada dos métodos em java podemos fazer com que o método function receba um parâmetro, que será ignorado: public <F,T> Function<F,T> function(T ignorado) {...} Assim podemos invocar: Function<Prova, Double> getNota = function(of(Prova.class).getNota()); Function<Prova, Double> getPeso = function(of(Prova.class).getPeso()); double media = mediaPonderada(provas, getNota, getPeso); Ou ainda (com um pequeno truque por causa da inferência de tipos genéricos do java): double media = mediaPonderada(provas, function(of(Prova.class).getNota()), function(of(Prova.class).getPeso())); ... public <T> Function<Prova,T> function(T ignorado) { return Funcional.function(ignorado); Extraindo o of(Prova.class) para uma constante chamada _, chegamos em algo bem próximo à sintaxe do Scala: private static Prova _ = of(Prova.class); ... double media = mediaPonderada (provas, function( _.getNota()), function( _.getPeso())); ... Aqui trocamos a complexidade da implementação – e até um pouco de performance – pela legibilidade e extensibilidade do uso desse código (poderíamos usar essa function em várias das classes do Guava, por exemplo), mesmo abusando de recursos e estruturas polêmicas – como ThreadLocal e métodos estáticos. E essa é uma abordagem que vem sendo usada em várias bibliotecas, como JUnit, Hamcrest e Mockito. É importante notar também que muitas das idéias desse código final vieram do Scala, e de outras linguagens funcionais, reforçando a importância de aprender várias linguagens de programação. Esse é um exemplo do malabarismo que podemos fazer com java e proxies, e o resultado ainda é um pouco difícil de ler. A abordagem do Java 8 vai ajudar a divulgar essas técnicas, com uma sintaxe muito mais adequada. O código completo pode ser visualizado aqui. Para quem gostou desse tipo de manipulação, existe o projeto LambdaJ que adiciona várias características funcionais ao java, deixando alguns códigos, como o que vimos acima, mais concisos e legíveis. --- Texto de total autoria de: Lucas Cavalcanti Retirado do Blog da Caelum: Um toque de programação funcional em Java | blog.caelum.com.br Compartilhar este post Link para o post Compartilhar em outros sites