Java 8: Entenda facilmente funções lambda, a principal novidade!
Rinaldo
Posted on March 24, 2019
A criação de Funções Lambda foi a principal novidade do Java 8, lançado em 2014! Hoje é praticamente obrigatório conhecer como elas funcionam e saber utilizá-las no seu código.
Prefere esse conteúdo em vídeo? Assista aqui!
Java 8 e programação funcional
Há algum tempo o JavaScript veio se estabelecendo como a linguagem padrão de desenvolvimento front-end. Ao mesmo tempo ocorre também o aparecimento e a popularização de linguagens como Scala, Kotlin e Python. Junto desses movimentos, a programação funcional começou a se tornar cada vez mais popular.
Com a intenção de trazer essa possibilidade também para o Java, foi criada a nova sintaxe de funções lambda. Se você nunca viu uma, aqui está:
() -> System.out.println("Hello World")
Se você nunca viu um código assim, não se assuste, você vai entender facilmente o que significa.
Java sem funções lambda
Se você programa Java há algum tempo, provavelmente já teve que escrever algum código parecido com esse:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
}).run();
}
Apesar de esse código funcionar perfeitamente, ele tem um grande problema: é gigante. Pense bem, foram necessárias 6 linhas de código para criar uma Thread e imprimir “Hello World”, algo que deveria ser completamente trivial. Agora, veja esse mesmo código utilizando uma função lambda:
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello World")).run();
}
O código acima é interpretado pelo compilador exatamente igual ao anterior, tendo uma única linha de código e sendo muito mais conciso. E o mais interessante aqui é que ainda estamos utilizando o mesmo construtor para criar uma Thread
. A própria IDE confirma isso:
O compilador sabe que essa função lambda é uma instância de Runnable
, mesmo que nós não deixemos isso explícito. Ok, mas como ele sabe disso?
Java 8 e o conceito SAM
Esse comportamento do compilador fica simples quando entendemos o conceito de Single Abstract Method, ou SAM. Basicamente, qualquer interface que tenha um único método está seguindo esse conceito. Logo, o compilador entende que sua função lambda é, na verdade, a implementação desse único método. Vamos ver de perto.
A interface Runnable
, por exemplo, possui apenas o método run
. Aqui está ela, copiada direto da JDK:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Sendo assim, ao utilizar uma função lambda como fizemos acima, o compilador entende que ela só pode ser a implementação do método run
.
Quanto à anotação @FunctionalInterface
presente nessa classe, ela é apenas informativa. Ela instrui ao compilador que gere um erro caso essa classe não preencha todos os requisitos para ser uma interface funcional, ou seja, uma que pode ser criada a partir de uma função lambda. Por exemplo, se essa interface tivesse dois métodos, ocorreria um erro de compilação. Apesar disso, essa anotação não é obrigatória. Você pode utilizar funções lambda com qualquer interface que atenda os pré-requisitos para ser considerada funcional.
Ok, então funções lambda servem apenas para eu diminuir a quantidade de linhas de código em casos como esse? Não. A verdade é que as funções lambda são muito mais úteis do que parecem. Os exemplos que dei acima são apenas para entender seu funcionamento, mas o que elas permitem fazer em Java é muito mais interessante.
Programação funcional
Com funções lambda é possível utilizar métodos muito conhecidos para quem utiliza JavaScript, como filter
, map
e forEach
.
Imagine, por exemplo, que você tem uma lista de números. Você deseja imprimir o valor dos 7 primeiros, multiplicado por 2, mas apenas se o número for par. Vejamos uma implementação possível com Java tradicional:
public static void main(String[] args) {
List<Integer> lista = Arrays.asList(1,5,8,7,4,6,3,2,1,8,5,7,4);
for (int i = 0; i < 7; i++) {
Integer numero = lista.get(i);
if (numero % 2 == 0) {
System.out.println(numero * 2);
}
}
}
Essa é uma implementação comum, funciona perfeitamente e, se você programa em Java há algum tempo, provavelmente está acostumado a vê-la. Porém, vamos ver como seria a mesma implementação com o uso de Streams do Java 8:
public static void main(String[] args) {
List<Integer> lista = Arrays.asList(1,5,8,7,4,6,3,2,1,8,5,7,4);
lista.stream()
.limit(7)
.filter(e -> e % 2 == 0)
.map(e -> e * 2)
.forEach(System.out::println);
}
Para quem não está acostumado, essa implementação pode parecer estranha a primeira vez. Porém, ela é muito mais concisa e delimitada. É possível saber exatamente todas as operações que estão sendo feitas nessa lista. Claramente existe um limite (limit
), um filtro (filter
), uma transformação (map
) e uma ação para cada item (forEach
).
Essa é estrutura da função lambda passada para o método filter
:
", depois do corpo da função."/>
A partir do momento que você aprende funções lambda e a nova API de Streams, você escreve um código muito mais fácil de entender, logo, mais fácil de dar manutenção. Além disso, muito menos propício a bugs.
A real vantagem do Java 8
De fato, a principal vantagem das funções lambdas no Java 8 foi permitir o uso de Streams. Sem elas seria praticamente impossível utilizar Streams com facilidade. O código seria tão grande e complicado, que simplesmente não seria útil. Se você está curioso para saber como ficaria a implementação acima sem o uso de funções lambda, aqui está. Por favor, nunca faça isso.
public static void main(String[] args) {
List<Integer> lista = Arrays.asList(1, 5, 8, 7, 4, 6, 3, 2, 1, 8, 5, 7, 4);
Stream<Integer> limit = lista.stream().limit(7);
// nunca escreva um código assim!
Stream<Integer> filter = limit.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer e) {
return e % 2 == 0;
}
});
Stream<Integer> map = filter.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer e) {
return e * 2;
}
});
map.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
}
Um caminho para o futuro do Java
Apesar de funções lambda terem sido extremamente úteis para o lançamento da API de Streams, isso foi apenas o começo. Várias APIs estão se tornando possíveis com o uso de funções lambda. O Java 9, por exemplo, trouxe Reactive Streams, o padrão implementado pela famosa biblioteca RxJava.
Em breve provavelmente estaremos implementando muitas coisas com programação funcional e funções lambdas no Java, como requisições HTTP, operações com arquivos e comunicação com banco de dados.
Quer saber um pouco mais da utilidade de funções lambda? Veja esse outro artigo sobre Streams e como utilizar funções lambda com coleções.
Parabéns! Agora você conhece expressões lambda do Java 8, e pode utilizar esse conhecimento para entregar seu projeto mais rápido e crescer na sua carreira.
Quer acessar esse conteúdo em vídeo também? Assista aqui!
Quer receber minhas melhores dicas para escrever código de alta qualidade e entregar projetos no prazo? Então acesse aqui.
Quer aprender a melhorar seu código diariamente? Então me segue no twitter: https://twitter.com/rinaldodev
Você já viu algum código com funções lambda recentemente? Ou conhece outras situações em que poderíamos utilizar? Deixe um comentário! Compartilhe também!
Gostou do que aprendeu? Compartilhe com outros Devs!
O post Java 8: Entenda facilmente funções lambda, a principal novidade! apareceu primeiro em rinaldo.dev.
Posted on March 24, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.