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)