Chegou o report API Trends 2024!
Leia AgoraThis is some text inside of a div block.
Você já pensou em Interfaces Funcionais e como elas funcionam em Java? Vamos vê-las agora!
Primeiro de tudo, você sabe o que é uma Interface Funcional?
Interfaces funcionais são interfaces que têm um método a ser implementado, em outras palavras, um método abstrato. Isso significa que toda interface criada que respeite esta premissa, tornando-se automaticamente uma interface funcional.
O compilador reconhece essas interfaces e permite que elas estejam disponíveis para que os desenvolvedores trabalhem, por exemplo, com expressões lambda.
Hoje, vamos falar sobre as principais Interfaces Funcionais apresentadas no JDK, que são
Verificando sua classe, podemos ver isso abaixo:
O que podemos concluir com isso?
A letra T significa que é genérica (genérico significa que pode ser de qualquer tipo), e neste caso significa que a operação get( ), quando executada, vai devolver algo para nós e pode ser de qualquer tipo.
Por outro lado, não precisamos passar um argumento. Em resumo, chamamos isso e recebemos algo - como um fornecedor (você entendeu o nome agora?).
Vamos ver um exemplo:
Aqui estamos chamando o método generate() do Stream API, que precisa de um Fornecedor para ser executado.
Assim, não estamos passando nada para o método usando os parênteses vazios '()', depois usando lambda '->' e finalmente executando o método - novo Random().nextInt() - que vai retornar algo para nós (neste caso, um número aleatório).
Aqui, nós acabamos de adicionar o limite e paraEach para mostrar o resultado no console.
Portanto, se executarmos o código, nós o obteremos:
É assim que o Fornecedor funciona - não precisamos fornecer nada e recebemos uma resposta.
Vamos verificar sua classe:
É uma interface simples, o oposto de Fornecedor. Ela recebe uma variável genérica, faz algo com ela e depois, não devolve nada.
Exemplo:
Temos uma lista da Integers e para imprimir estes números no console, podemos usar o .forEach para nos ajudar com isso.
Ele está recebendo uma variável - número - e fazendo algo com ela. Neste caso, ela está imprimindo no console e não devolvendo nada para o usuário.
Assim como um consumidor. Recebe algo, faz algo e é tudo.
O BiConsumer segue as mesmas regras, mas recebe dois argumentos.
No exemplo acima, estamos recebendo dois valores inteiros e apenas imprimindo-os no console e devolvendo nada.
Agora, vamos dar uma olhada nas classes Predicado e BiPredicado:
A classe Predicado tem um método chamado teste, que recebe um argumento, e retorna um booleano.
Podemos concluir que este método é utilizado para validar hipóteses. Vamos ver no código:
Para este exemplo, temos uma Lista de Integers que é composta com os números - 1, 2, 3, 4 e 5.
E mais uma vez, vamos utilizar o Stream API. Vamos converter nossa lista para um Stream através do .stream() - então vamos usar o .filter() - e é neste pequenote que a magia acontece com o Predicado.
Nosso filtro de método precisa de um Predicado para ser executado e, como vimos antes, ele validará uma hipótese e retornará Verdadeiro ou Falso para nós.
Neste método, estamos obtendo o número e verificando se ele é divisível por 2 - o que significa que é um número par. Se for verdade, então executaremos o forEach, caso contrário, ele será ignorado.
É assim que os métodos Predicate funcionam: eles testam hipóteses e retornam a você se forem verdadeiros ou falsos.
Se executarmos este código, obtemos este resultado:
Acabou de imprimir os números pares, como esperado.
E em relação ao BiPredicate, temos o mesmo comportamento, a única diferença é que vamos receber dois parâmetros a serem verificados em vez de um. Confira abaixo:
Exemplo:
Aqui temos um BiPredicado recebendo dois parâmetros - palavra e tamanho - e verificando se eles têm o mesmo valor.
No primeiro teste, vamos receber o Verdadeiro, e no segundo, vamos receber o Falso.
A função da interface funcional é a mais genérica. Ela tem a definição mais básica de uma função - ela recebe algo e retorna algo.
Vamos dar uma olhada na classe:
Vamos começar com a Função. Aqui podemos ver que o método aplicado recebe uma variável genérica - lembre-se que genérica significa que pode ser de qualquer tipo - e retornará outra variável genérica.
Uma coisa importante aqui é que mesmo que as palavras genéricas sejam diferentes - T e R - isso não significa que o método apply() não possa receber e devolver o mesmo tipo.
Então, vamos ao código - Mais uma vez, vamos usar o Stream API para este exemplo.
Aqui vamos usar o método .map(), que precisa de uma Função para ser executada.
Portanto, neste código:
Estamos recebendo o número, que é um valor Inteiro, e devolvendo um Duplo. Portanto, estamos passando um argumento para o mapa e recebendo uma resposta.
*Neste caso, estamos dando um valor Inteiro e recebendo como resposta um valor Duplo.
Mas, por exemplo, nós poderíamos fazer isso:
É um valor Inteiro como argumento e seu retorno é um valor Inteiro também. A função permite isso. Com relação à BiFunção, ela tem o mesmo comportamento, exceto que recebe dois argumentos assim como o BiPredicado.
Vamos verificar nos exemplos abaixo:
No primeiro exemplo (sumNumbers), temos uma BiFunção que recebe dois argumentos - tipo Inteiro - e retorna também um tipo Inteiro. Estamos fazendo uma soma e quando usamos o apply() resulta no número 3 (1 + 2).
A seguir, temos outro exemplo, mas, desta vez, ele está devolvendo um valor duplo. Obtivemos 2 números inteiros e, depois de chamar o Math.pow(), ele retornou um valor Duplo. Uma vez executado o método apply(), obteremos o resultado: 4.0 (Duplo)
Resumindo - damos um ou dois argumentos e recebemos algo em troca. O significado básico de uma função.
Vamos dar uma olhada em sua classe:
O UnaryOperator estende uma função - mas em seu construtor está definido o mesmo tipo de argumentos, você notou isso?
We have UnaryOperator <T> extending to Function <T, T>.
Está definindo o tipo permitido para esta interface, e como todos estes genéricos são a mesma letra (T), isso significa que só nos é permitido trabalhar com o mesmo tipo de variáveis - nossos argumentos e retornos devem ser do mesmo tipo para trabalhar no UnaryOperator.
UnaryOperator tem o mesmo comportamento que a Função, mas só funciona com os mesmos tipos.
Por exemplo:
Você pode apenas definir um tipo em seu construtor, e ele será estendido à Função, e como podemos ver no código acima, podemos apenas trabalhar com variáveis do mesmo tipo.
É a mesma coisa que as outras.
Ela se estende à BiFunction. Portanto, vamos receber dois argumentos e devolver um - todos com o mesmo tipo.
Vamos checar isto:
Usamos novamente o Stream API.
Agora, vamos usar o método .reduce(). Ele receberá dois argumentos e retornará um valor com o mesmo tipo.
Em nosso caso, temos um conjunto de números - 1 a 5 - e vamos somar todos os valores.
*Como reduzir os retornos um valor Opcional, vamos usar o .ifPresent() para imprimir nosso resultado.
Portanto, como resultado, teremos aqui: 15 (1 + 2 + 3 + 4 + 5)
*Vai se repetir até que toda a matriz passe por ela.
Para embrulhar as coisas, vejamos um exemplo de tudo trabalhando em conjunto:
Apenas uma última coisa - as interfaces funcionais também aceitam Referência de Método - o código poderia ser assim:
É isso aí! Espero que isso possa ajudá-lo a criar um código melhor e mais limpo! Assim como ajudar você a entender como funcionam as funções.
Estamos prontos para guiar o seu negócio rumo ao futuro, com a solução certa para você se beneficiar do potencial das APIs e integrações modernas.
Confira os conteúdos produzidos pela nossa equipe
Conte com nosso apoio para levar as melhores integrações para o seu negócio, com soluções e equipes profissionais que são referência no mercado.