Fala, pessoal! Tranquilo? Hoje a gente vai ver um pouco mais sobre interfaces funcionais no Java, e alguns exemplos de como usar elas.

O que são as interfaces funcionais?

As interfaces funcionais do Java são interfaces que possuem apenas um método abstrato. Elas foram introduzidas a partir da versão 8 do Java e são amplamente utilizadas na programação funcional. As interfaces funcionais permitem a criação de expressões lambda, que são funções anônimas que podem ser passadas como parâmetro para outros métodos. Isso permite a escrita de código mais conciso e legível.

Alguns exemplos de interfaces funcionais incluem: Consumer, Supplier, Function, UnaryOperator e Predicate. Essas interfaces funcionais são utilizadas em diversas APIs do Java, como por exemplo, Stream, Collections, entre outras. Elas possibilitam a utilização de funções anônimas e expressões lambda para realizar operações de forma mais elegante e sucinta.

Link das interfaces funcionais do Java: https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

O que cada uma dessas interfaces fazem?

Consumer<T>

Representa uma operação que recebe um parâmetro do tipo T e não retorna resultado, e quem recebe um Consumer é o forEach().

Exemplo de uso:

list.forEach(System.out::println);

Que é a mesma coisa que:

**list.forEach( x -> System.out.println(x));**

Consumer docs: https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html

Function<T, R>

Representa uma operação que recebe um parâmetro do tipo T e retorna um resultado do tipo R. E quem recebe um Function é o map().

Exemplo de uso:

list.stream().map(String::toUpperCase).collect(Collectors.toList());

Function docs: https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html

Predicate<T>

Representa uma operação que recebe um parâmetro do tipo T e retorna um valor booleano. E quem recebe um Predicate é o filter().

Exemplo de uso:

list.stream().filter(x -> x.length() > 3).collect(Collectors.toList());

Predicate docs: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

Supplier<T>

Representa uma operação que não recebe parâmetros e retorna um resultado do tipo T. E quem recebe um Supplier é o generate().

Exemplo de uso:

Stream.generate(Math::random).limit(10).forEach(System.out::println);

Supplier docs: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

UnaryOperator<T>

Representa uma operação que recebe e retorna um parâmetro do mesmo tipo T. E quem recebe um UnaryOperator é o replaceAll().

Exemplo de uso: list.replaceAll(String::toUpperCase);

UnaryOperator docs: https://docs.oracle.com/javase/8/docs/api/java/util/function/UnaryOperator.html

Acredito que agora já dê pra notar que as interfaces funcionais são bem usadas, mas nem todos sabem que realmente estão usando interfaces funcionais. Entretando, essas são as interfaces mais usadas atualmente, o Java possui diversas outras interfaces funcionais que não veremos aqui.

Como as interfaces funcionais funcionam?

Ao utilizar uma interface funcional, o compilador verifica o tipo do objeto que está sendo passado para o método e compara com a assinatura do método definido na interface. Se o tipo corresponde, o método é invocado.

Por debaixo dos panos, o Java utiliza uma implementação de proxy para gerar uma classe anônima que implementa a interface funcional. Essa classe anônima possui uma única instância do método definido na interface, e ao invocá-lo, o código da expressão lambda é executado.

Em outras palavras, a interface funcional é usada como uma forma de permitir que uma expressão lambda seja passada como argumento para um método que espera uma referência de método. O Java cria automaticamente uma classe anônima que implementa a interface funcional e invoca a expressão lambda quando o método é chamado.

Resumindo, você passa uma expressão lambda e ela serve como implementação do método abstrato da interface funcional. Simples, não?

Exemplo

Agora vamos ver 2 dos vários exemplos de se usar interfaces funcionais:

Vamos usar uma interface funcional chamada Predicate para nos ajudar nisso, ela receberá um número do tipo Integer e retornará um boolean.

Nossa expressão verifica se um número é par ou não:

Predicate<Integer> isPar = numero -> numero % 2 == 0;

Agora, nós chamaremos o método test() e nesse método passamos nosso número para verificar se realmente esse número é par ou não, retornara true se for par e false caso seja impar. Mas como esse método test() sabe o que vai fazer, ou seja, qual a implementação dele? Simples, nós passamos a implementação, quando nós fizemos isso edicate<Integer> isPar = numero -> numero % 2 == 0; então estamos falando que o método abstrato dentro desse Predicate assumirá a implementação dessa expressão Lambda.

Testando:
System.out.println(isPar.test(2)); // True

System.out.println(isPar.test(5)); // False

Então agora um outro exemplo é, criarmos expressões Lambdas para usarmos como parâmetro quando for necessário, da seguinte forma:

List<Double> notas = Arrays.asList(5.0, 6.0, 7.5, 6.5);      
Consumer<Double> printar = nota -> System.out.println(nota);
notas.forEach(printar);

Nesse exemplo criamos uma lista, após isso, criamos uma expressão Lambda do tipo Consumer e então atribuímos ela ao parâmetro do forEach, que pra quem não sabe, recebe um Consumer.

Métodos Default

As interfaces funcionais além de apenas um método abstrato também tem outros métodos, que chamamos de métodos default, eles são métodos que possuem implementação, e cada interface tem seus métodos default específicos deles, a maneira mais simples de verificarmos o que cada um desses métodos faz, é realmente entrar na documentação da interface em específico e verificar, ou até mesmo entrar pela IDE na interface e verificar como foi a implementação daquele método.

Conclusão

Em conclusão nós podemos ter uma boa noção de como utilizar e o que são realmente as interfaces funcionais, assim podendo usar elas não somente como parâmetros para métodos já conhecidos como, map(), filter() e entre outros, sem realmente entender o que está acontecendo. Espero que esses conceitos tenham ficado claros, um grande abraço e até a próxima.