Java 8: Lambdas e Interfaces Funcionais

Java 8: Lambdas e Interfaces Funcionais

Em Java 8 foi adicionada a habilidade de escrever código usando Programação Funcional, a qual é uma maneira de escrever código de forma mais declarativa. Você especifica o que deseja ao invés de lidar com o estado dos objetos. Você foca mais em expressões do que em loops.

Programação Funcional usa expressões lambda para escrever código. Você pode pensar na expressão lambda como um método sem nome. Ele tem parâmetros e um corpo como métodos possuem. Expressões lambda são comumente referenciadas como Lambdas, de forma abreviada.


Exemplo de Lambda

Nosso objetivo é imprimir todos os animais em uma lista de acordo com algum critério. Nós iremos mostrar como fazer isto sem lambdas para ilustrar como lambdas são úteis. Nós começaremos com a classe Animal:


A classe Animal têm três variáveis de instância, as quais são inseridas no construtor. Além disso, existem dois métodos que recuperam o estado do objeto, se ele pode saltar (hop) ou nadar (swim), também tem um método toString(), dessa forma nós podemos facilmente identificar o Animal nos programas.

Nós planejamos escrever diferentes checagens, portanto precisamos de uma interface. Por agora, devemos apenas lembrar que uma interface especifica métodos que nossa classe necessita implementar:

A primeira coisa que nós precisamos checar é se o Animal pode saltar (can hop). Nós provemos uma classe que pode checar isso:

Esta classe pode parecer simples e na verdade é. Isso é parte do problema que lambdas resolvem. Agora nós temos tudo que precisamos para escrever nosso código para encontrar os animais que saltam (can hop):


O método print() é bem específico - ele pode checar por qualquer Trait. Ele não deveria saber o que nós estamos pesquisando especificamente a fim de imprimir a lista de animais. Agora o que aconteceria se quiséssemos imprimir os animais que nadam? Nós teremos que escrever outra classe, CheckIfSwims. Então nós precisamos adicionar uma nova linha que instância a classe.

Por que nós não especificamos a lógica a qual nos importamos bem aqui? Nós poderíamos repetir a classe inteira e fazer você procurar a única linha que mudou. Em vez disso, nós vamos mostrar a você. Nós poderíamos substituir a referida linha pelo seguinte código:

print(animals, a -> a.canHop());

Nós estamos dizendo ao Java que nos preocupamos apenas com animais que saltam (hop). Não requer muita imaginação para descobrir como nós poderíamos adicionar lógica para recuperar os animais que nadam (swim). Nós temos que adicionar apenas uma linha de código - sem necessidade de uma classe extra para fazer algo simples:

print(animals, a -> a.canSwim());

E sobre animais que não nadam?

print(animals, a -> !a.canSwim());

Sintaxe Lambda

Uma das mais simples expressões lambda que você pode escrever é a seguinte:


a -> !a.canHop()

A sintaxe do lambda é complicada porque muitas partes são opcionais. Estas duas linhas fazem exatamente a mesma coisa:


Vamos verificar o que ocorre aqui, o primeiro exemplo possui três partes:

  • Um único parâmetro especificando com o nome a;
  • O operador de seta para separar o parâmetro e o corpo;
  • Um corpo que chama um único método e retorna o resultado desse método.

O segundo exemplo mostra a forma mais verbosa do lambda que retorna um boolean:

  • Um único parâmetro especificando com o nome a, com o tipo Animal;
  • O operador de seta para separar o parâmetro do corpo;
  • Um corpo que possui uma ou mais linhas de código, incluindo um ponto e vírgula e uma declaração de retorno.

Introduzindo Functional Interfaces

Lambdas trabalham com interfaces que possuem um único método abstrato, assim são chamadas interfaces funcionais, esse método obedece a uma regra (rule) chamada de Single Abstract Method (SAM).

Predicate

Você pode imaginar que nós poderíamos criar várias interfaces como essa utilizando lambdas. Nós queremos testar Animals e Strings e Plants e qualquer outra coisa que nos deparamos. Felizmente, Java reconhece que isto é um problema comum e nos fornece tal interface. Isto está no pacote java.util.function e a essência dele é a seguinte:


Esta interface se parece bastante com nosso método test(Animal). A única diferença é que ele usa o tipo T em vez de Animal. Esta é a sintaxe de generics. É como se nós criássemos um ArrayList e tivéssemos que especificar qualquer tipo que vai nele. Isto significa que nós não precisamos mais de nossa própria interface e podemos colocar qualquer coisa relacionada à nossa pesquisa em uma classe:

Neste código, nós esperamos ter um Predicate que usa o tipo Animal. Nós podemos usá-lo sem ter de escrever código extra.

Consumer

A interface funcional Consumer possui um método que você precisa saber:

void accept(T t)

Por quê você deseja receber um valor e não retorná-lo? Uma razão comum é quando se imprime uma mensagem:

Consumer<String> consumer = x -> System.out.println(x);

Nós declaramos uma funcionalidade para imprimir o valor que no foi dado. Tudo bem que nós não temos um valor ainda. Quando o Consumer é chamado, o valor vai ser fornecido e então imprimido. Vamos dar uma olhada em um código que usa um Consumer:


Este código imprime “Hello World”. O método print() aceita um Consumer que sabe como imprimir um valor. Quando o método accept() é chamado, o lambda na verdade é executado, imprimindo o valor.

Supplier

A interface funcional Supplier possui somente um método:

T get()

Um bom caso de uso para um Supplier é ao gerar valores. Aqui temos dois exemplos:


O primeiro exemplo retorna 42 toda vez que o lambda é chamado. O segundo gera um número randômico toda vez que é chamado. Este poderia ser o mesmo número, mas é mais provável que seja um número diferente. Vamos dar uma olhada em um código que usa Supplier:


Quando o método returnNumber() é chamado, ele invoca o lambda para recuperar o valor desejado. Neste caso, o método retorna 42.

Comparator

Esta interface é uma interface funcional já que tem apenas um método não implementado. Ela possui vários métodos static e default a fim de facilitar a escrita de comparators complexos. A interface Comparator existia antes dos lambdas serem adicionados ao Java. Como resultado, ela é um pacote diferente. Você pode encontrar o Comparator em java.util.

Você precisa apenas saber sobre o método compare(). Você consegue descobrir se ele ordena em ordem ascendente ou descendente?

Comparator<Integer> ints = (i1, i2) -> i1 - i2;

O Comparator ints usa ordem natural de ordenamento. Se o primeiro número é maior, ele irá retornar um número positivo. Suponha que nós estamos comparando 5 e 3. O Comparator subtrai 5-3 e retorna 2. Este é um número positivo, o qual significa que o primeiro número é maior e nós estamos ordenando em ordem ascendente.

Vamos analisar outro caso. Você sabia que estas duas declarações poderiam ordenar em ordem ascendente ou descendente?


Ambos Comparators, na verdade, fazem a mesma coisa: ordenam em ordem descendente. No primeiro exemplo, a chamada ao compareTo() é “para trás”, fazendo ele descendente. No segundo exemplo, a chamada usa a ordem default; entretanto, o Comparator aplica um sinal negativo ao resultado, o qual reverte-o.

Tenha certeza que você entendeu a tabela abaixo para identificar qual tipo de lambda você pode usar.

Espero que este guia tenha sido útil.

Até logo!

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.