Interfaces Funcionais do Java 8

Bem-vindo ao tutorial de exemplo de interfaces funcionais do Java 8. O Java sempre foi uma linguagem de programação Orientada a Objetos. Isso significa que tudo na programação java gira em torno de Objetos (exceto alguns tipos primitivos para simplicidade). Não temos apenas funções no java, elas são parte da Classe e precisamos usar a classe/objeto para invocar qualquer função.

Interfaces Funcionais do Java 8

Se olharmos para algumas outras linguagens de programação, como C++, JavaScript; elas são chamadas de linguagem de programação funcional porque podemos escrever funções e usá-las quando necessário. Algumas dessas linguagens suportam tanto a Programação Orientada a Objetos quanto a Programação Funcional. Ser orientado a objetos não é ruim, mas traz muita verbosidade ao programa. Por exemplo, digamos que temos que criar uma instância de Runnable. Geralmente, fazemos isso usando classes anônimas como abaixo.

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

Se você olhar para o código acima, a parte real que é útil é o código dentro do método run(). Todo o resto do código é por causa da forma como os programas java são estruturados. Interfaces Funcionais do Java 8 e Expressões Lambda nos ajudam a escrever códigos menores e mais limpos, removendo muito código repetitivo.

Interface Funcional do Java 8

Uma interface com exatamente um método abstrato é chamada de Interface Funcional. A anotação @FunctionalInterface é adicionada para que possamos marcar uma interface como interface funcional. Não é obrigatório usá-la, mas é uma prática recomendada usá-la com interfaces funcionais para evitar a adição acidental de métodos extras. Se a interface for anotada com a anotação @FunctionalInterface e tentarmos ter mais de um método abstrato, ocorrerá um erro de compilação. O principal benefício das interfaces funcionais do Java 8 é que podemos usar expressões lambda para instanciá-las e evitar o uso de implementações de classes anônimas volumosas. A API de Coleções do Java 8 foi reescrita e uma nova API de Stream foi introduzida, que utiliza muitas interfaces funcionais. O Java 8 definiu muitas interfaces funcionais no pacote java.util.function. Algumas das interfaces funcionais úteis do Java 8 são Consumer, Supplier, Function e Predicate. Você pode encontrar mais detalhes sobre elas em Exemplo de Stream do Java 8. java.lang.Runnable é um ótimo exemplo de interface funcional com um único método abstrato run(). O trecho de código abaixo fornece algumas orientações para interfaces funcionais:

interface Foo { boolean equals(Object obj); }
// Não funcional porque equals já é um membro implícito (classe Object)

interface Comparator {
 boolean equals(Object obj);
 int compare(T o1, T o2);
}
// Funcional porque Comparator tem apenas um método abstrato não-Object

interface Foo {
  int m();
  Object clone();
}
// Não funcional porque o método Object.clone não é público

interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Funcional: dois métodos, mas têm a mesma assinatura

interface X { Iterable m(Iterable arg); }
interface Y { Iterable m(Iterable arg); }
interface Z extends X, Y {}
// Funcional: Y.m é uma subassinatura e substituível pelo tipo de retorno

interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Não funcional: nenhum método tem uma subassinatura de todos os métodos abstratos

interface X { int m(Iterable arg, Class c); }
interface Y { int m(Iterable arg, Class c); }
interface Z extends X, Y {}
// Não funcional: nenhum método tem uma subassinatura de todos os métodos abstratos

interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
// Erro do compilador: nenhum método é substituível pelo tipo de retorno

interface Foo { void m(T arg); }
interface Bar { void m(T arg); }
interface FooBar extends Foo, Bar {}
// Erro do compilador: assinaturas diferentes, mesma eliminação

Expressão Lambda

Expressões Lambda são a maneira através da qual podemos visualizar a programação funcional no mundo orientado a objetos do Java. Objetos são a base da linguagem de programação Java e nunca podemos ter uma função sem um Objeto, é por isso que a linguagem Java oferece suporte para usar expressões lambda apenas com interfaces funcionais. Como só há uma função abstrata nas interfaces funcionais, não há confusão ao aplicar a expressão lambda ao método. A sintaxe das Expressões Lambda é (argumento) -> (corpo). Agora vamos ver como podemos escrever o Runnable anônimo acima usando a expressão lambda.

Runnable r1 = () -> System.out.println("My Runnable");

Vamos tentar entender o que está acontecendo na expressão lambda acima.

  • A interface Runnable é uma interface funcional, é por isso que podemos usar uma expressão lambda para criar sua instância.
  • Como o método run() não recebe argumentos, nossa expressão lambda também não tem argumento.
  • Assim como nos blocos if-else, podemos evitar chaves ({}) já que temos uma única instrução no corpo do método. Para múltiplas instruções, teríamos que usar chaves como em qualquer outro método.

Por que precisamos de Expressão Lambda

  1. Linhas de Código Reduzidas Um dos claros benefícios de usar expressão lambda é que a quantidade de código é reduzida, já vimos como podemos facilmente criar uma instância de uma interface funcional usando expressão lambda em vez de usar uma classe anônima.

  2. Suporte à Execução Sequencial e Paralela Outra vantagem de usar expressões lambda é que podemos aproveitar o suporte às operações sequenciais e paralelas da API Stream. Para explicar isso, vamos usar um exemplo simples onde precisamos escrever um método para testar se um número passado é primo ou não. Tradicionalmente, escreveríamos o código assim:

    //Abordagem tradicional
    private static boolean isPrime(int number) {		
    	if(number < 2) return false;
    	for(int i=2; i

    O problema com o código acima é que ele é sequencial por natureza, se o número for muito grande, levará uma quantidade significativa de tempo. Outro problema com o código é que existem tantos pontos de saída e não é legível. Vamos ver como podemos escrever o mesmo método usando expressões lambda e a API Stream.

    //Abordagem declarativa
    private static boolean isPrime(int number) {		
    	return number > 1
    			&& IntStream.range(2, number).noneMatch(
    					index -> number % index == 0);
    }
    

    IntStream é uma sequência de elementos int primitivos que suporta operações de agregação sequenciais e paralelas. Esta é a especialização primitiva de int de Stream. Para mais legibilidade, também podemos escrever o método assim:

    private static boolean isPrime(int number) {
    	IntPredicate isDivisible = index -> number % index == 0;
    	
    	return number > 1
    			&& IntStream.range(2, number).noneMatch(
    					isDivisible);
    }
    

    Se você não está familiarizado com IntStream, o método range() retorna um IntStream ordenado sequencialmente de startInclusive (inclusive) a endExclusive (exclusivo) com um passo incremental de 1. O método noneMatch() retorna se nenhum dos elementos deste stream corresponde ao predicado fornecido. Pode não avaliar o predicado em todos os elementos se não for necessário para determinar o resultado.

  3. Passar comportamentos para métodos Vamos ver como podemos usar expressões lambda para passar o comportamento de um método com um exemplo simples. Digamos que temos que escrever um método para somar os números em uma lista se eles corresponderem a um critério dado. Podemos usar o Predicate e escrever um método como abaixo.

    public static int somaComCondição(List<Integer> números, Predicate<Integer> predicado) {
    	    return números.parallelStream()
    	    		.filter(predicado)
    	    		.mapToInt(i -> i)
    	    		.sum();
    	}
    

    Uso de exemplo:

    // soma de todos os números
    somaComCondição(números, n -> true)
    // soma de todos os números pares
    somaComCondição(números, i -> i%2==0)
    // soma de todos os números maiores que 5
    somaComCondição(números, i -> i>5)
    
  4. Maior Eficiência com Preguiça Mais uma vantagem de usar expressões lambda é a avaliação preguiçosa, por exemplo, vamos dizer que precisamos escrever um método para descobrir o maior número ímpar no intervalo de 3 a 11 e retornar o quadrado dele. Geralmente escreveríamos o código para este método assim:

    private static int encontrarQuadradoDoMaiorImpar(List<Integer> numeros) {
    		int max = 0;
    		for (int i : numeros) {
    			if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
    				max = i;
    			}
    		}
    		return max * max;
    	}
    

    O programa acima sempre será executado em ordem sequencial, mas podemos usar a API Stream para alcançar isso e obter o benefício da busca preguiçosa. Vamos ver como podemos reescrever este código de forma funcional usando a API Stream e expressões lambda.

    public static int encontrarQuadradoDoMaiorImpar(List<Integer> numeros) {
    		return numeros.stream()
    				.filter(NumberTest::isImpar) 		// Predicate é uma interface funcional e
    				.filter(NumberTest::isMaiorQue3)	// estamos usando lambdas para inicializá-la
    				.filter(NumberTest::isMenorQue11)	// em vez de classes internas anônimas
    				.max(Comparator.naturalOrder())
    				.map(i -> i * i)
    				.get();
    	}
    
    	public static boolean isImpar(int i) {
    		return i % 2 != 0;
    	}
    	
    	public static boolean isMaiorQue3(int i){
    		return i > 3;
    	}
    	
    	public static boolean isMenorQue11(int i){
    		return i < 11;
    	}
    

    Se você está surpreso com o operador de dois pontos duplos (::), ele foi introduzido no Java 8 e usado para referências de método. O Compilador Java cuida de mapear os argumentos para o método chamado. É uma forma abreviada das expressões lambda i -> isMaiorQue3(i) ou i -> NumberTest.isMaiorQue3(i).

Exemplos de Expressões Lambda

Abaixo, estou fornecendo alguns trechos de código para expressões lambda com pequenos comentários explicativos.

() -> {}                     // No parameters; void result

() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body

 // Corpo de bloco complexo com múltiplos retornos 
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1                 // Single inferred-type argument, same as below
x -> x+1                   // Parenthesis optional for single inferred-type case

(String s) -> s.length()   // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length()              // Single inferred-type argument
t -> { t.start(); }          // Single inferred-type argument

(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

Referências de Método e Construtor

A method reference is used to refer to a method without invoking it; a constructor reference is similarly used to refer to a constructor without creating a new instance of the named class or array type. Examples of method and constructor references:

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new

Isso é tudo para o Tutorial de Interfaces Funcionais do Java 8 e Expressões Lambda. Eu sugeriria fortemente que você olhasse para utilizá-lo porque essa sintaxe é nova no Java e levará algum tempo para entendê-la. Você também deve conferir as Funcionalidades do Java 8 para aprender sobre todas as melhorias e mudanças no lançamento do Java 8.

Source:
https://www.digitalocean.com/community/tutorials/java-8-functional-interfaces