[Dicas de Programação] Java 8 – Functional Interfaces

Uma das novas funcionalidades introduzidas por Java 8 são as Interfaces funcionais (functional interfaces).

Elas são interfaces que possuem um único método abstrato. Porém, métodos sobrescrevendo métodos da classe java.lang.Object não são considerados métodos da própria interface. E default method não é considerado método abstrato. Portanto ao utilizar esses métodos a interface não deixa de ser uma interface funcional.

Pode-se utilizar a annotation @FunctionalInterface para garantir que uma Interface não deixará de ser uma Interface funcional.

Exemplo de Interface Funcional:

@FunctionalInterface
public interface TestingFunctionalInterface {
    String fiMethod(String test);
}

O Java 8 já possui algumas interfaces funcionais prontas. As mais famosas são:

  1. Consumer
    Recebe um objeto/valor, faz algo com ele. Mas não retorna nada.
    Ex: foreach( )
  2. BiConsumer
    Funciona igual ao Consumer, porém ao invés de receber um único valor, recebe dois. Faz algo com eles, e não retorna nada.
  3. Supplier
    Não recebe nenhum argumento, porém devolve objetos/valores.
    Ex: Stream.generate( )
  4. Predicate
    Recebe 1 argumento e retorna true ou false.
    Ex: stream( ). filter( )
  5. BiPredicate
    Igualmente ao predicate retorna true ou false, porém recebe 2 argumentos.
  6. Function
    Recebe 1 argumento e retorna um objeto/valor.
    Ex: stream( ).map( )
  7. BiFunction
    Assim como a function retorna um objeto/valor, mas recebe 2 argumentos.
  8. UnaryOperator
    Igual a function, porém o argumento deverá ser do mesmo tipo do retorno.
  9. BinaryOperator
    Igual a BiFunction, porém os 2 argumentos de entrada e o retorno todos devem ter o mesmo tipo.
    Ex: stream( ).reduce( )
 public static void main(String[] args) {

        List<String> testList = new ArrayList<>();

        List<String> stringList = Arrays.asList("A","B","C","D","E");

        String test = Stream.generate(() -> "x").limit(110).collect(Collectors.joining());//Supplier
        System.out.println(test);

        testList = stringList.stream().filter(word -> word.equals("A")).collect(Collectors.toList()); //Predicate

        testList.forEach(word -> System.out.println(word)); //Consumer

        stringList.stream().map(word -> word.equals("A")); //Function

        stringList.stream().reduce((w1,w2) -> w2 + w1); //Reduce
    }

Algumas interfaces anteriores ao Java 8 também acabaram se enquadrando na categoria de Interfaces Funcionais, por possuírem a estrutura requisitada, são elas:

  • Runnable
  • Callable

Espero que esse conteúdo tenha te ajudado em algo.

Se tem alguma sugestão para melhorar esse artigo, ou encontrou algum erro nele, por favor deixe um comentário, que eu corrijo. Se gostou, por favor compartilhe nas suas redes e ajude o blog.

Te vejo na próxima!

Fontes:
https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html

https://www.youtube.com/watch?v=Ht0eto2mEpc

https://www.baeldung.com/java-8-lambda-expressions-tips

https://www.baeldung.com/java-8-functional-interfaces

[Dicas de Programação] Java 8 – Default Methods

Default Methods foram adicionados entre as funcionalidades que podem ser utilizadas nas Interfaces no Java 8. Antes disso toda classe que implementava uma Interface deveria implementar todos os métodos “assinados” por ela, para atender ao contrato. Isso várias vezes gerava muito código repetitivo por causa dos métodos.

Uma solução para isso era utilizar o design pattern Strategy. Criando uma outra classe somente para implementar esse método repetitivo.

Outra solução seria utilizar uma classe abstrata, ao invés de uma interface. Lembrando que ao se utilizar uma classe abstrata não temos garantia que as classes filhas implementarão todos os métodos da classe mãe, podendo levar a desencontros.

Os Default Methods surgem para dar uma solução mais simples para esse problema. Permitindo que possamos realizar a implementação de um método direto na Interface, sem a necessidade de que todas as suas filhas implementem esses métodos específicos, garantindo pelo menos uma implementação padrão (default) pra ele. Deixando para as classes filhas decidirem se querem ou não uma implementação diferente para esse método padrão (default method).

Exemplo:

public interface DefaultInterface {
    default String defaultMethod(){
        return "HelloWorld";
    }
    
    String method();
}

public class ImplementsDefaultInterface implements DefaultInterface{

    @Override
    public String method() {
        return "method";
    }
}

Espero que esse conteúdo tenha te ajudado em algo.

Se tem alguma sugestão para melhorar esse artigo, ou encontrou algum erro nele, por favor deixe um comentário, que eu corrijo. Se gostou, por favor compartilhe nas suas redes e ajude o blog.

Te vejo na próxima!

Update1: Como sugestão de um pessoal foda, uma outra vantagem que ficou de fora é: Poder adicionar novos métodos em Interfaces existentes que já foram implementadas por outras classes, sem quebrar nada.

Referência: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html