Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

quitZAUMMM

[Resolvido] Um toque de Programação Funcional em Java

Recommended Posts

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

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.