Java 8 — Functional Interfaces — Tornando o Java mais legal

Marcelo Carvalho
8 min readJun 14, 2018

--

“Que título mais tosco”. Sim, eu sei, só agora que ele ficou legal haha. Mas a verdade é que foi graças a essa nova funcionalidade no ambiente do Java que conseguimos fazer coisas mais dinâmicas, nativamente claro, pois já existiam algumas bibliotecas que adicionavam isso.

Confesso que toda vez que me pegava pensando em como explicar Functional Interfaces, eu acabava pensando em coisas como ter que explicar Streams, Functions e Lambdas. E na minha cabeça eu não conseguia pensar numa forma de explicar um sem citar a outra.

A verdade é que realmente não dá. A impressão que eu tenho é a de que sem esse artifício das Functional Interfaces todo o resto não faria sentido. Para mim, é mais um conceito do que algo concreto. E é por essa razão que existia uma dificuldade de explicar outras features do Java 8.

O que são?

Functional Interfaces são todas as interfaces que possuem um método à ser implementados, ou em outras palavras, um método abstrato. Isso quer dizer que várias interfaces que já existiam e que atendiam a essa premissa, automaticamente se tornaram interfaces funcionais.

O compilador consegue reconhecer essas interfaces e disponibilizá-las para o desenvolvedor trabalhar, por exemplo, com Lambdas.

Functional Interfaces também podem conter um ou mais Default Methods. Uma outra novidade do Java 8 que será tratada em outro artigo.

@FunctionalInterface

Se o compilador reconhece as interfaces, por que existe uma anotação para isso?

Essa anotação existe para que o desenvolvedor possa forçar um erro de compilação, caso a interface não atenda os requisitos de uma Functional Interface. Que são:

  • Ser uma interface e não outro tipo de objeto.
  • Conter apenas um método abstrato.

Esses erros serão demonstrados na última parte desse artigo

Como construir uma Functional Interface?

Acredito que já tenhamos uma ideia do que é, com o que já foi dito até agora. Mas como todos sabemos, para aprender é sempre bom botar a mão na massa. Não se preocupe é tudo bem simples.

Para isso usaremos um projeto bem simples com Java 8 (Obviamente haha), o projeto criado no artigo Spring Boot — Como criar uma aplicação do zero — Parte 1 pode ser utilizado para isso. Não será necessário fazer o exemplo todo do artigo citado, somente criar o projeto com o nome functional-Interfaces. Caso ainda não tenha visto vai lá, eu espero.

Após ter criado o projeto teremos a estrutura a seguir (se você usou o projeto do outro artigo, claro):

Estrutura do nosso projeto

Esse estrutura é bem simples e a partir daqui vamos usar as Functional Interfaces de 3 formas.

Na primeira vamos criar uma interface, adicionaremos à ela a anotação @FunctionalInterface. Na segunda utilizaremos uma interface que já existia antes da versão 8 e que atendeu os requisitos de uma interface funcional. E por último, uma das novas interfaces do Java 8.

Uma interface novinha

Vamos criar uma nova interface se chamará “DisplayFunction”. Adicionaremos nela a anotação @FunctionalInterface. Criaremos um método abstrato com o nome “mostrar”, esse método será void. No final, ela ficará como abaixo.

package com.mvalho.medium.example;

@FunctionalInterface
public interface DisplayFunction {
void mostrar(String string);
}

Agora vamos abrir a classe principal “FunctionalInterfaceApplication ” para usarmos nossa interface funcional.

Vou criar um bean do CommandLineRunner, esse bean é chamado logo depois que o Spring Boot roda, e qualquer coisa implementada dentro dele é executado. Um bom lugar para criarmos alguns exemplos.

O intuito desse artigo é ser simples, poderíamos também usar a classe de teste para rodas nossos exemplos. Se você já for mais hardcore, feel free. 😄

Como faríamos antes do java 8 para implementarmos essa interface? Uma classe que implementaria nossa interface, certo? ou ainda, uma classe anônima como veremos a seguir:

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
DisplayFunction displayFunction = new DisplayFunction() {
@Override
public void mostrar(String string) {
System.out.println("Olá, " + string);
}
};

displayFunction.mostrar("Marcelo");
};
}
}

Como você pode ver, temos algumas linhas para imprimir algo na tela.

Agora usaremos lambda para fazer a mesma coisa. Veja:

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
DisplayFunction displayFunction = string -> System.out.println("Olá, " + string);

displayFunction.mostrar("Marcelo");
};
}
}
Resultado: Aplicação usando a Functional Interface com lambda

Veja que além de economizarmos algumas linhas, nós também deixamos a implementação mais dinâmica. Outra coisa também é que se quiséssemos uma nova implementação teríamos que, ou criar uma classe nova ou criamos uma nova classe anônima.

Dessa forma, implementar algo novo baseado na DisplayFunction seria bem rápido, veja abaixo:

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
DisplayFunction displayFunction = string -> System.out.println("Olá, " + string);

displayFunction.mostrar("Marcelo");

DisplayFunction displayFunction2 = string -> System.out.println("Você tem " + string + " anos");

displayFunction2.mostrar("33");
};
}
}
Resultado: Aplicação implementando 2 versões da interface.

Rapidamente implementamos 2 versões da DisplayFunction e com uma leitura muito melhor do código, do que seria se tivessemos usado uma classe anônima.

O melhor uso das interfaces funcionais, na minha opinião, não seria dessa forma, mas, como parametros de outros métodos. Da forma como vemos no pacote Stream.

Interfaces que já existiam

Como dito anteriormente, toda interface existente que atendeu os termos de uma interface funcional, é automaticamente identificada como tal pela JVM. Assim interfaces como Runnable, Readable, Iterable, Comparable e outras, tornaram-se Functional Interfaces.

No nosso exemplo implementaremos a interface Runnable como uma classe anônima. Após, iremos usá-la como parâmetro de uma nova Thread. O resultado deverá ser impresso no console como “Task #1 está rodando” acompanhado de outras mensagens com o tempo que essa thread irá pausar e também outra mensagem informando que está finalizada.

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
Runnable task1 = new Runnable() {
@Override
public void run() {
System.out.println("Task #1 está rodando");
try {
long sleep = (long) (Math.random() * 10 * 1000);

System.out.println("Pausando task #1 por " + (sleep / 1000) + " segundos");
Thread.sleep(sleep);
System.out.println("Task #1 finalizada");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};

new Thread(task1).start();
};
}
}
Resultado: A thread foi pausada por 2 segundos e finalizada

Agora vamos substituir a classe anônima por lambda.

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
Runnable task1 = () -> {
System.out.println("Task #1 está rodando");
try {
long sleep = (long) (Math.random() * 10 * 1000);

System.out.println("Pausando task #1 por " + (sleep / 1000) + " segundos");
Thread.sleep(sleep);
System.out.println("Task #1 finalizada");
} catch (InterruptedException e) {
e.printStackTrace();
}
};

new Thread(task1).start();
};
}
}

Não tivemos grandes mudanças na implementação e aparentemente temos o mesmo tamanho de arquivo. A diferença está, que não usamos o operador new e também nós não tivemos que implementar diretamente o método run, pois, como uma Functional Interface, o compilador já entende que o lambda irá implementar o único método que essa interface possui.

... new Runnable() {
@Override
public void run() {....

Por mais que a implementação tenha ficado quase que na mesma, isso mostra que a interface que já existia anteriormente funciona mesmo sem estar com a anotação @FuncionalInterface.

Novas Functional Interfaces

Com o Java 8 vieram algumas novas interfaces que são especificamente criadas para servirem como Functional Interfaces (inclusive, anotadas como tal).

As Functions são o melhor exemplo disso, são interfaces criadas especificamente para serem usadas com lambdas e estão todas presentes no pacote java.util.function. Nesse pacote temos praticamente tudo que precisaremos para implementarmos qualquer operação. Se precisarmos de uma operação que retorne um booleano, temos a Predicate. Se precisarmos receber algo como argumento e produzir um resultado, usaremos a interface Function. Caso apenas precisarmos receber um argumento sem a necessidade de retornarmos nada, Consumer. Com todas as interfaces que temos nesse pacote, é bem improvável a necessidade de se criar um interface funcional para a maioria das operações que precisaremos. Inclusive temos interfaces mais especializadas, como as que são especializadas em tipos númericos.

Todas essas novas interfaces são muito usadas nos novos métodos do pacote Stream. Mas podemos usar também em nossos próprios métodos.

No nosso exemplo vamos somar dois números, e esses números não devem ser menor ou igual a zero. O resultado será apresentado no console. Veja:

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.function.BiFunction;
import java.util.function.BiPredicate;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
Integer n1 = 10;
Integer n2 = 25;

BiPredicate<Integer, Integer> testaNumeros = (numero1, numero2) -> numero1 > 0 && numero2 > 0;
BiFunction<Integer, Integer, Integer> soma = (numero1, numero2) -> numero1 + numero2;

if (testaNumeros.test(n1, n2)) {
System.out.println(soma.apply(n1, n2));
}
};
}
}

Nesse caso, estamos fazendo operações que retornam integer, como dito anteriormente, temos interfaces especializadas para tipos númericos, vamos ver como ficaria.

package com.mvalho.medium.example;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.function.BiPredicate;
import java.util.function.ToIntBiFunction;

@SpringBootApplication
public class FunctionalInterfaceApplication {

public static void main(String[] args) {
SpringApplication.run(FunctionalInterfaceApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner() {
return args -> {
Integer n1 = 10;
Integer n2 = 25;

BiPredicate<Integer, Integer> testaNumeros = (numero1, numero2) -> numero1 > 0 && numero2 > 0;
ToIntBiFunction<Integer, Integer> soma = (numero1, numero2) -> numero1 + numero2;

if (testaNumeros.test(n1, n2)) {
System.out.println(soma.applyAsInt(n1, n2));
}
};
}
}

Ao invés de passarmos 3 tipos para a interface, passamos somente dois, pois essa interface já espera que o retorno seja um integer. A seguir o resultado:

Foi bem breve os exemplos de interfaces funcionar na prática. Mas isso será melhor visto nos próximos artigos, onde veremos o pacote Stream e usaremos muito as implementações dessas interfaces.

Erros de Compilação

Nessa ultima parte do artigo vamos ver os erros que o compilador gera quando anotamos uma classe com @FunctionalInterface e quebramos o contrato.

Anotando Uma Classe

functional_interface\src\main\java\com\mvalho\medium\example\ClasseDeTeste.java:6: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
ClasseDeTeste is not a functional interface

Adicionando Outro Método Abstrato

functional_interface\src\main\java\com\mvalho\medium\example\DisplayFunction.java:3: error: Unexpect
ed @FunctionalInterface annotation
@FunctionalInterface
^
DisplayFunction is not a functional interface
multiple non-overriding abstract methods found in interface DisplayFunction

Interface sem nenhum Método Abstrato

functional_interface\src\main\java\com\mvalho\medium\example\DisplayFunction.java:3: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
DisplayFunction is not a functional interface
no abstract method found in interface DisplayFunction

Conclusão

Espero que com o que tentei passar aqui de forma sucinta, possa ter ajudado a você que está procurando uma explicação sobre essa feature adicionada no Java 8. Nos próximos artigos que irão abordar outras novidades do Java 8, algumas questões que ficaram para trás serão respondidas. Assim eu espero! 😅

--

--