Recursos do Java 8 com exemplos

O Java 8 foi lançado em 18 de março de 2014. Isso foi há muito tempo, mas ainda muitos projetos estão sendo executados no Java 8. Isso se deve ao fato de que foi um lançamento importante com muitos recursos novos. Vamos dar uma olhada em todos os recursos emocionantes e principais do Java 8 com exemplos de código.

Visão geral rápida dos recursos do Java 8

Alguns dos recursos importantes do Java 8 são;

  1. forEach() método na interface Iterable
  2. métodos padrão e estáticos em Interfaces
  3. Interfaces Funcionais e Expressões Lambda
  4. API de Fluxo Java para Operações de Dados em Massa em Coleções
  5. API de Tempo Java
  6. Melhorias na API de Coleção
  7. Melhorias na API de Concorrência
  8. Melhorias na API de E/S Java

Vamos dar uma breve olhada nesses recursos do Java 8. Vou fornecer alguns trechos de código para melhor entender os recursos de uma maneira simples.

1. forEach() método na interface Iterable

Sempre que precisamos percorrer uma Coleção, precisamos criar um Iterador cujo único propósito é iterar sobre ela, e então temos a lógica de negócios em um loop para cada um dos elementos na Coleção. Podemos receber uma ConcurrentModificationException se o iterador não for usado corretamente.

O Java 8 introduziu o método forEach na interface java.lang.Iterable para que, ao escrevermos código, nos concentremos na lógica de negócios. O método forEach recebe um objeto java.util.function.Consumer como argumento, o que ajuda a ter nossa lógica de negócios em um local separado que podemos reutilizar. Vamos ver o uso do forEach com um exemplo simples.

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

	public static void main(String[] args) {
		
		// criando uma Coleção de exemplo
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		// percorrendo usando Iterator
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		// percorrendo através do método forEach de Iterable com classe anônima
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		// percorrendo com implementação da interface Consumer
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

// Implementação de Consumer que pode ser reutilizada
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

O número de linhas pode aumentar, mas o método forEach ajuda a ter a lógica para iteração e a lógica de negócios em lugares separados, resultando em uma maior separação de preocupações e código mais limpo.

2. métodos padrão e estáticos em Interfaces

Se você ler os detalhes do método forEach cuidadosamente, perceberá que ele é definido na interface Iterable, mas sabemos que interfaces não podem ter um corpo de método. A partir do Java 8, as interfaces foram aprimoradas para ter um método com implementação. Podemos usar a palavra-chave default e static para criar interfaces com implementação de método. A implementação do método forEach na interface Iterable é:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Sabemos que o Java não fornece herança múltipla em Classes porque isso leva ao Problema do Diamante. Então, como isso será tratado com interfaces agora, já que as interfaces agora são semelhantes às classes abstratas?

A solução é que o compilador lançará uma exceção nesse cenário e teremos que fornecer a lógica de implementação na classe que implementa as interfaces.

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
	
	static void print(String str){
		System.out.println("Printing "+str);
	}
	
	//tentar substituir o método Object gera erro de compilação como
	//"Um método padrão não pode substituir um método de java.lang.Object"
	
//	default String toString(){
//		return "i1";
//	}
	
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

Observe que ambas as interfaces têm um método comum log() com lógica de implementação.

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	// MyClass não irá compilar sem ter sua própria implementação log()
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

Como você pode ver, a Interface1 tem implementação de método estático que é usada na implementação do método MyClass.log(). O Java 8 usa métodos default e static ​​intensamente na API de Coleções e métodos default são adicionados para que nosso código permaneça retrocompatível.

Se alguma classe na hierarquia tiver um método com a mesma assinatura, então os métodos default se tornam irrelevantes. Object é a classe base, então se tivermos métodos default equals(), hashCode() na interface, eles se tornarão irrelevantes. Por isso, para uma melhor clareza, as interfaces não podem ter métodos default de Object.

Para detalhes completos das mudanças de interface no Java 8, por favor, leia Alterações de interface do Java 8.

3. Interfaces Funcionais e Expressões Lambda

Se você observar o código de interface acima, você notará a anotação @FunctionalInterface. Interfaces funcionais são um novo conceito introduzido no Java 8. Uma interface com exatamente um método abstrato se torna uma Interface Funcional. Não precisamos usar a anotação @FunctionalInterface para marcar uma interface como uma Interface Funcional.

A anotação @FunctionalInterface é uma facilidade para evitar a adição acidental de métodos abstratos nas interfaces funcionais. Você pode pensar nisso como a anotação @Override e é uma prática recomendada usá-la. java.lang.Runnable com um único método abstrato run() é um ótimo exemplo de uma interface funcional.

Um dos principais benefícios da interface funcional é a possibilidade de usar expressões lambda para instanciá-las. Podemos instanciar uma interface com uma classe anônima, mas o código parece volumoso.

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

Como as interfaces funcionais têm apenas um método, as expressões lambda podem fornecer facilmente a implementação do método. Só precisamos fornecer argumentos do método e lógica de negócio. Por exemplo, podemos escrever a implementação acima usando expressão lambda como:

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

Se você tiver apenas uma instrução na implementação do método, também não precisamos de chaves. Por exemplo, a classe anônima Interface1 acima pode ser instanciada usando lambda da seguinte forma:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

As expressões lambda são uma forma de criar facilmente classes anônimas de interfaces funcionais. Não há benefícios em tempo de execução ao usar expressões lambda, então eu as usarei com cautela, pois não me importo de escrever algumas linhas extras de código.

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

Você pode ler o tutorial completo em Tutorial de Expressões Lambda do Java 8.

4. API de Stream do Java para Operações de Dados em Massa em Coleções

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

A interface Collection foi estendida com os métodos padrão stream() e parallelStream() para obter o Stream para execução sequencial e paralela. Vamos ver o uso deles com um exemplo simples.

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

	public static void main(String[] args) {
		
		List<Integer> myList = new ArrayList<>();
		for(int i=0; i<100; i++) myList.add(i);
		
		//stream sequencial
		Stream<Integer> sequentialStream = myList.stream();
		
		//stream paralelo
		Stream<Integer> parallelStream = myList.parallelStream();
		
		//usando lambda com a API de Stream, exemplo de filtro
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		//usando lambda no forEach
		highNums.forEach(p -> System.out.println("High Nums parallel="+p));
		
		Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
		highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

	}

}

Se você executar o código do exemplo acima, obterá uma saída como esta:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

Observe que os valores do processamento paralelo não estão em ordem, então o processamento paralelo será muito útil ao trabalhar com coleções enormes.

Não é possível cobrir tudo sobre a API Stream neste post, você pode ler tudo sobre a API Stream em Tutorial de Exemplo da API Stream Java 8.

5. API Java Time

Sempre foi difícil trabalhar com Data, Hora e Fusos Horários em java. Não havia uma abordagem padrão ou API em java para data e hora em Java. Uma das boas adições no Java 8 é o pacote java.time que simplificará o processo de trabalhar com tempo em java.

Só de olhar para os pacotes da API Java Time, consigo perceber que eles serão muito fáceis de usar. Ele tem alguns subpacotes java.time.format que fornece classes para imprimir e analisar datas e horas e java.time.zone fornece suporte para fusos horários e suas regras.

A nova API de Tempo prefere enums em vez de constantes inteiras para meses e dias da semana. Uma das classes úteis é DateTimeFormatter para converter objetos DateTime em strings. Para um tutorial completo, vá para Tutorial de Exemplo da API de Data e Hora Java.

6. Melhorias na API de Coleções

Já vimos o método forEach() e a API Stream para coleções. Alguns novos métodos adicionados na API de Coleções são:

  • Iterator método padrão forEachRemaining(Consumer ação) para executar a ação fornecida para cada elemento restante até que todos os elementos tenham sido processados ou a ação lance uma exceção.
  • Collection método padrão removeIf(Predicate filtro) para remover todos os elementos desta coleção que satisfaçam o predicado fornecido.
  • Collection método spliterator() que retorna uma instância de Spliterator que pode ser usada para percorrer elementos sequencialmente ou em paralelo.
  • Map métodos replaceAll()compute()merge().
  • Melhoria de Desempenho para a classe HashMap com Colisões de Chaves

7. Melhorias na API de Concorrência

Algumas importantes melhorias na API concorrente são:

  • ConcurrentHashMap métodos compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() e search().
  • CompletableFuture que pode ser explicitamente completado (definindo seu valor e status).
  • Executors método newWorkStealingPool() para criar um pool de threads baseado em roubo de trabalho usando todos os processadores disponíveis como seu nível de paralelismo alvo.

8. Melhorias no Java IO

Algumas melhorias de IO conhecidas por mim são:

  • Files.list(Path dir) que retorna um Stream preenchido de forma preguiçosa, cujos elementos são as entradas no diretório.
  • Files.lines(Path path) que lê todas as linhas de um arquivo como um Stream.
  • Files.find() que retorna um Stream preenchido de forma preguiçosa com Path, pesquisando arquivos em uma árvore de arquivos enraizada em um determinado arquivo inicial.
  • BufferedReader.lines() que retorna um Stream, cujos elementos são linhas lidas deste BufferedReader.

Melhorias diversas na API principal do Java 8

Algumas melhorias diversas na API que podem ser úteis são:

  1. ThreadLocal método estático withInitial(Supplier supplier) para criar instâncias facilmente.
  2. A interface Comparator foi estendida com muitos métodos padrão e estáticos para ordenação natural, ordem reversa, etc.
  3. Métodos min(), max() e sum() nas classes wrapper Integer, Long e Double.
  4. Métodos logicalAnd(), logicalOr() e logicalXor() na classe Boolean.
  5. O método stream() do ZipFile para obter um Stream ordenado sobre as entradas do arquivo ZIP. As entradas aparecem no Stream na ordem em que aparecem no diretório central do arquivo ZIP.
  6. Vários métodos utilitários na classe Math.
  7. O comando jjs foi adicionado para invocar o Engine Nashorn.
  8. O comando jdeps foi adicionado para analisar arquivos de classe
  9. A ponte JDBC-ODBC foi removida.
  10. O espaço de memória PermGen foi removido

Isso é tudo para os recursos do Java 8 com programas de exemplo. Se eu perdi alguns recursos importantes do Java 8, por favor me avise através de comentários.

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples