O java 8 introduziu várias funcionalidades interessantes, como novas APIs de Stream, Optional, Date and Time e Functional Interfaces.
Este artigo tem o objetivo de fazer uma breve introdução sobre as Interfaces Funcionais e apresentar os tipos mais comuns disponíveis a partir do JDK 8.
Interfaces funcionais permitem trabalharmos com closures no Java. Closure é uma função que conhece o escopo na qual foi criada. Ou seja, a função tem acesso às suas variáveis e às variáveis e parâmetros da função externa que a criou. Com as interfaces funcionais, também é possível passarmos funções por parâmetros ou retorná-las.
Uma Interface Funcional deve obrigatoriamente possuir um e apenas um método abstrato, mas que pode ter outros métodos default.
Além disso, recomenda-se anotar a interface com @FunctionalInterface
. Esta anotação irá ajudar o compilador a sinalizar um erro caso você tente adicionar mais de um método abstrato. Incluir essa anotação é opcional, desde que respeite a regra de ter apenas um método abstrato.
Exemplo:
@FunctionalInterface public interface Operacao { int calcular(int a, int b); }
Para definir uma função a partir de uma interface, usamos expressões lambda. Lambda nada mais é do que uma função anônima, que não possui nome e nem está vinculada como um método de classe. Abaixo algumas diferentes formas de criar uma função usando expressão lambda:
- Com as chaves: quando o corpo da função tiver várias instruções.
Funcao fn = (a, b) -> { System.out.println("Realizando a soma..."); // instrução 1 return a + b; // instrução 2 };
- Sem as chaves: quando a função tiver apenas uma instrução. O resultado da instrução
a + b
será retornado na chamada da função.
Funcao fn = (a, b) -> a + b;
- Sem os parênteses do argumento: quando há apenas 1 parâmetro na função, podemos omitir os parênteses.
Funcao fn = x -> x * 2;
Assim, utilizando o exemplo acima da interface Operacao
, criamos várias operações matemáticas utilizando lambdas:
Operacao soma = (a, b) -> a + b; Operacao subtracao = (a, b) -> a - b; Operacao divisao = (a, b) -> a / b; Operacao multiplicacao = (a, b) -> a * b; System.out.println(soma.calcular(1, 2)); // 3 System.out.println(subtracao.calcular(5, 1)); // 4 System.out.println(divisao.calcular(10, 5)); // 2 System.out.println(multiplicacao.calcular(2, 5)); // 10
Analisando o exemplo acima, podemos notar que a mesma interface foi utilizada para várias operações matemáticas e o código ficou bem reduzido e mais legível!
Mas se você não quer ficar criando interfaces funcionais no seu projeto, também pode utilizar das já disponiveis no próprio Java. Vamos conhecer algumas?
java.util.function.Function<T, R>
Esta função recebe um parâmetro do tipo T e retorna um valor do tipo R.
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t); }
Exemplo:
Function<Integer, Float> dividePorDois = x -> ((float) x / 2); System.out.println(dividePorDois.apply(15)); // 7.5
java.util.function.Consumer<T>
Esta função recebe um parâmetro do tipo T, mas não retorna nada.
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); }
Exemplo:
Consumer<String> exibeMensagem = str -> System.out.println(str); // ou Consumer<String> exibeMensagem2 = System.out::println; exibeMensagem.accept("Hello World!"); // Hello World! exibeMensagem2.accept("Hello World!"); // Hello World!
Note que, como o método println
possui a mesma assinatura de método da interface Consumer<String>
(recebe uma String e não retorna nada) e há somente uma instrução na função, podemos passar a referência do próprio método println
. Isso se chama Method Reference.
java.util.function.Supplier<T>
Esta função não recebe parâmetros e retorna um valor do tipo T.
@FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
Exemplo:
Supplier<LocalDate> dataAtual = () -> LocalDate.now(); System.out.println(dataAtual.get()); // 2021-06-02
java.util.function.Predicate<T>
Esta função recebe um parâmetro do tipo T e retorna um valor booleano.
@FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); }
Exemplo:
Predicate<LocalDate> maiorDeIdade = dataNascimento -> { return dataNascimento.until(LocalDate.now(), ChronoUnit.YEARS) >= 18; }; System.out.println(maiorDeIdade.test(LocalDate.of(1985, 10, 5))); // true
As interfaces disponíveis no JDK abordadas neste artigo possuem especializações que permitem receber dois parâmetros (adicionando o prefixo Bi
) ou trabalhar diretamente com tipos primitivos (adicionando o prefixo Int
, Long
ou Double
). Exemplos: BiFunction, IntFunction, LongFunction e DoubleFunction.
Vimos os quatro principais tipos de interfaces funcionais. Recomendo que você explore as diversas opções que a JDK fornece e aplicá-las sempre que possível nos projetos.
Os exemplos utilizados neste artigo estão no repositório: https://github.com/andrebuarque/functional-interfaces-java.
Até logo!
Top comments (0)