Por trás da programação funcional do JAVA 8

Jeremias Pereira dos Reis
Dev Cave
Published in
5 min readFeb 2, 2017

Ultimamente, o termo programação funcional tem sido bastante usado e discutido na maioria das comunidades de desenvolvedores. Basta uma simples pesquisa no Google que veremos inúmeros resultados, diversos posts onde tentam explicar a origem, o que consiste, benefícios etc. Por esse motivo não vamos abordar a parte conceitual de programação funcional nem como programar funcional com java, vamos direto ao ponto, como o java 8 se comporta com programação funcional e se podemos considerá-lo uma linguagem funcional, o objetivo deste post é analisar se o java 8 de fato é funcional ou se ele ainda será.

Sim, em pleno 2017 eu ainda falando de java 8 (culpa da Oracle que não lança logo o 9 para testarmos e escrevermos posts novos). Então, se você programa em java 8 há algum tempo, já foi até a quinta página de resultados do Google pesquisando sobre java 8 e como funciona a a programação funcional nele, provavelmente esse post será mais do mesmo para você, caso use java 8 e seu conhecimento teórico e conceitual ainda seja fraco (e deseja melhorar), continue lendo, é para você mesmo que estou escrevendo.

Não precisa ser um super fã de java para saber que nesta última atualização, tivemos algumas novidades um tanto que curiosas, a principal delas; o lambda (também conhecido como função de “setinhas”), muita gente comemorou a sua chegada um pouco tardia, já que não era nenhuma novidade em outras linguagens do mesmo propósito, inclusive o .NET (seu maior rival). Vamos falar a maior parte do post sobre lambdas, vamos ver alguns pontos interessantes, o porque demorou tanto para chegar ao java e como conseguiram implementar essa nova funcionalidade.

Lambda

Na maioria das linguagens de paradigma não-funcional que implementam lambda, ele é basicamente definido pela capacidade de declarar funções anônimas. Se formos um pouco mais a fundo sobre cálculo lambda, o conceito de uma função não precisar ser nomeada explicitamente é apenas a ponta do iceberg, e se pesquisarmos um pouco mais sobre cálculo lambda no âmbito computacional, veremos que programar utilizando o paradigma funcional e lambdas é muito mais que usar as famosas “setinhas” para declarar uma função.

Um caso muito comum na utilização de lambdas (ou funções anôminas) é a ordenação de uma lista (se você já utiliza java 8 e ainda implementa anônimamente a interface Comparator para ordenar uma lista, por favor, pare tudo e repense sua vida). Como ordenávamos uma lista no java 7:

Collections.sort(cars, new Comparator<Car>() {
@Override
public int compare(Car car1, Car car2) {
return car1.getId() - car2.getId();
}
});

Normal, o método sort da classe Collections recebe como parâmetro uma lista (List) e um comparador (Comparator), como o comparador é para um único propósito e utilização, ou seja, muito provável que não iremos utilizar ele em outra parte do código, para não criarmos um novo arquivo apenas para uma classe que implemente a interface Comparator que iremos utilizar apenas uma única vez, criamos a implementação desta interface em tempo de execução, criamos uma instância da nova classe e passamos a referência desse objeto como parâmetro, as famosas classes anônimas. Agora, vejamos como podemos ordenar a mesma lista no java 8:

Collections.sort(cars, (car1, car2) -> car1.getId() - car2.getId());

O fato de reduzirmos de 6 para 1 linha de implementação e ainda melhorar a legibilidade do código sem impacto nenhum na performance, já são argumentos suficentes para utilizarmos lambda no lugar de classes anônimas (quando possível) e sem questionamentos. A situação é a mesma do primeiro exemplo, muito provável que só iremos utilizar essa implementação nesta parte do código, então implementamos a comparação dos elementos dessa lista em tempo de execução.

Se analisarmos a documentação da classe Collections no java 7 e no java 8, nada mudou, a assinatura do método sort, continua a mesma coisa, recebendo uma lista e um comparador, porém, até quem não conhece nada de lambda, consegue perceber no exemplo acima que o segundo parâmetro não tem como ser um objeto, conseguimos facilmente identificar uma operação de comparação, ou seja, ainda estamos implementando um método que compara dois objetos do tipo carro, porém, agora estamos apenas declarando uma função, a famosa função anônima do java 8 (ou lambda), e não mais uma classe anônima. O segredo está na interface Comparator, que ganhou a anotação @FunctionalInterface. Podemos perceber que não é em todos os lugares que conseguimos utilizar lambdas, o java permite apenas em implementações de interfaces funcionais, na verdade, não precisa necessáriamente estar anotada com @FunctionalInterface, basta ser uma interface e possuir apenas um método que o compilador irá entender. Então vamos entender o que o java faz para compilar um lambda.

OBS: O intellij já está reportando warnings quando você implementa uma interface funcional através de classe anônima.

Compilação de lambdas

Com certeza, a pergunta que não quer calar é, como que uma linguagem imperativa e orientada a objetos, fortemente tipada, ou melhor, como que uma linguagem cuja classe é sua entidade real onde todo código deve existir sob o domínio de uma classe, todo código deve ser referenciado através de uma classe, nos permite de uma hora para outra, referenciar uma função anônima e ainda com tipagem de parâmetros opcional? Primeiramente, sim, realmente tudo indica que estamos referenciando uma função função anônima, e que essa função não pertence a uma classe, mas não é bem assim que o compilador faz para a JVM entender esse código.

O fato do uso de lambdas estar restrito apenas com interfaces funcionais não é coincidência, como uma interface funcional possui apenas um método, já da pra perceber o que o compilador faz por baixo dos panos né. Mas ainda não é tão simples assim.

Quando o compilador encontra uma expressão lambda no código, a primeira coisa que ele faz é, utilizando LambdaMetafactory extrair a lambda para uma função tipada e encontrar sua devida interface funcional na qual o método irá implementar, a tipagem dos parâmetros e a interface funcional correta é obtida lexicalmente, após isso, graças a JSR 292 implementada no java 7, a JVM consegue fazer toda a mágica para chamar o método dinamicamente através do invokedynamic. Uma explicação muito bem detalhada sobre como o compilador converte expressões lambda pode ser encontrada aqui.

A mesma coisa acontece quando referenciamos um método:

cars.stream().sorted(Comparator.comparingInt(Car::getId));

O compilador irá utilizar a classe LambdaMetafactory para referenciar o método que será conhecido e chamado pela JVM em tempo de execução.

Afinal, é funcional ou não?

Sim, e não, se olharmos como desenvolvedores, quando utilizamos lambda estamos referenciando uma função, por mais que no final o compilador transforme tudo isso, coloque dentro de uma classe e tudo volta como era antes, para quem está desenvolvendo, lambda é a referência de uma função anônima, a operação acima, é a passagem de um método como parâmetro. Porém não podemos afirmar com todas as forças do universo que o java 8 é uma linguagem com paradigma funcional, ainda não podemos assinar funções a uma variável, ainda não temos total controle sobre estado de objetos etc.

Agora se formos binários, não, java não é uma linguagem de paradigma funcional e se a intenção for se tornar funcional um dia, o que não é verdade, está muito longe disso. Há mais de 15 anos o java vem conseguindo combinar muito bem o paradigma de orientação a objetos com alguns outros, e com o funcional não será diferente. Ser funcional é muito mais do que permitir uma pequena abstração de lambda, talvez, muitos recursos de imperatividade e até mesmo de orientação a objetos deveriam ser sacrificados.

O lambda veio para o java por suas qualidades (que não são poucas), a facilidade e praticidade de utlizar sem impacto em performance, a legibilidade que deixa no código, a economia de linhas por função (que não é pouco) etc, o mesmo acontece com streams e referência de métodos. Ou seja, o java apenas implementou o que a programação funcional possui de melhor, com adaptações, restrições, porém mantendo seu legado.

--

--