Interfaces funcionais - Java 8
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
- Fornecedor
- Consumidor e BiConsumidor
- Predicado e BiPredicado
- Função e BiFunção
- UnaryOperator e BinaryOperator
Fornecedor
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.
Consumidor e BiConsumidor
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.
Predicado e BiPredicado
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.
Função e BiFunção
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.
UnaryOperator e BinaryOperator
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:
- Aqui estamos obtendo apenas os números pares;
- Em seguida, transformando-os em Duplo;
- Em seguida, somando todas elas;
- E se houver um número no final, nós o imprimimos no console.
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.
Inicie sua jornada conosco
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.
Conteúdos relacionados
Confira os conteúdos produzidos pela nossa equipe
Sua história de sucesso começa aqui
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.