Caratteristiche di Java 8 con Esempi

Java 8 è stato rilasciato il 18 marzo 2014. È passato molto tempo, ma ancora molti progetti sono in esecuzione su Java 8. È perché è stato un rilascio importante con molte nuove funzionalità. Vediamo tutte le funzionalità principali ed eccitanti di Java 8 con esempi di codice.

Panoramica rapida delle funzionalità di Java 8

Alcune delle importanti funzionalità di Java 8 sono;

  1. metodo forEach() nell’interfaccia Iterable
  2. metodi predefiniti e statici nelle interfacce
  3. Interfacce funzionali ed espressioni Lambda
  4. API Stream di Java per operazioni su dati in blocco su collezioni
  5. API Time di Java
  6. Miglioramenti all’API Collection
  7. Miglioramenti all’API di concorrenza
  8. Miglioramenti all’API Java IO

Facciamo una breve panoramica di queste funzionalità di Java 8. Fornirò alcuni frammenti di codice per una migliore comprensione delle funzionalità in modo semplice.

1. Metodo forEach() nell’interfaccia Iterable

Ogni volta che abbiamo bisogno di attraversare una collezione, dobbiamo creare un iteratore il cui scopo principale è iterare su di essa, e poi abbiamo la logica di business in un ciclo per ciascuno degli elementi nella collezione. Potremmo ottenere una ConcurrentModificationException se l’iteratore non viene utilizzato correttamente.

Java 8 ha introdotto il metodo forEach nell’interfaccia java.lang.Iterable in modo che, durante la scrittura del codice, ci concentriamo sulla logica di business. Il metodo forEach prende un oggetto java.util.function.Consumer come argomento, quindi aiuta a avere la nostra logica di business in un luogo separato che possiamo riutilizzare. Vediamo l’utilizzo di forEach con un semplice esempio.

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) {
		
		//creazione di una collezione di esempio
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//attraversamento utilizzando l'iteratore
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//attraversamento tramite il metodo forEach di Iterable con classe anonima
		myList.forEach(new Consumer<Integer>() {

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

		});
		
		//attraversamento con implementazione dell'interfaccia Consumer
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//implementazione Consumer che può essere riutilizzata
class MyConsumer implements Consumer<Integer>{

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

Il numero di righe potrebbe aumentare, ma il metodo forEach aiuta a avere la logica per l’iterazione e la logica di business in un luogo separato, risultando in una maggiore separazione delle preoccupazioni e codice più pulito.

Metodi predefiniti e statici nelle interfacce

Se leggi attentamente i dettagli del metodo forEach, noterai che è definito nell’interfaccia Iterable ma sappiamo che le interfacce non possono avere un corpo di metodo. A partire da Java 8, le interfacce sono potenziate per avere un metodo con implementazione. Possiamo utilizzare la parola chiave default e static per creare interfacce con implementazione del metodo. L’implementazione del metodo forEach nell’interfaccia Iterable è:

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

Sappiamo che Java non fornisce ereditarietà multipla nelle classi perché porta al problema del diamante. Quindi come verrà gestito con le interfacce ora, dal momento che le interfacce sono ora simili alle classi astratte?

La soluzione è che il compilatore solleverà un’eccezione in questo scenario e dovremo fornire la logica di implementazione nella classe che implementa le interfacce.

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);
	}
	
	//tentativo di sovrascrivere il metodo Object dà errore in tempo di compilazione come
	//"Un metodo predefinito non può sovrascrivere un metodo da 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);
	}

}

Si noti che entrambe le interfacce hanno un metodo comune log() con una logica di implementazione.

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	// MyClass non compilerà senza avere la propria implementazione log()
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

Come puoi vedere, Interface1 ha un’implementazione del metodo statico che viene utilizzata nell’implementazione del metodo MyClass.log(). Java 8 utilizza pesantemente i metodi default e statici nell’API Collection e i metodi di default vengono aggiunti in modo che il nostro codice rimanga retrocompatibile.

Se una classe nell’ierarchia ha un metodo con la stessa firma, allora i metodi di default diventano irrilevanti. Object è la classe di base, quindi se abbiamo metodi di default equals(), hashCode() nell’interfaccia, diventeranno irrilevanti. Ecco perché per una migliore chiarezza, non è consentito avere metodi di default di Object nelle interfacce.

Per i dettagli completi sulle modifiche delle interfacce in Java 8, si prega di leggere Modifiche alle interfacce di Java 8.

3. Interfacce Funzionali ed Espressioni Lambda

Se noti il codice dell’interfaccia sopra, noterai l’annotazione @FunctionalInterface. Le interfacce funzionali sono un nuovo concetto introdotto in Java 8. Un’interfaccia con esattamente un metodo astratto diventa un’Interfaccia Funzionale. Non è necessario utilizzare l’annotazione @FunctionalInterface per contrassegnare un’interfaccia come Interfaccia Funzionale.

@FunctionalInterface è una struttura per evitare l’aggiunta accidentale di metodi astratti nelle interfacce funzionali. Puoi pensarlo come l’annotazione @Override ed è una pratica consigliata usarla. java.lang.Runnable con un singolo metodo astratto run() è un ottimo esempio di interfaccia funzionale.

Uno dei principali vantaggi dell’interfaccia funzionale è la possibilità di utilizzare espressioni lambda per istanziarle. Possiamo istanziare un’interfaccia con una classe anonima ma il codice sembra ingombrante.

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

Dato che le interfacce funzionali hanno solo un metodo, le espressioni lambda possono facilmente fornire l’implementazione del metodo. È sufficiente fornire gli argomenti del metodo e la logica di business. Ad esempio, possiamo scrivere l’implementazione sopra usando un’espressione lambda come:

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

Se hai una singola istruzione nell’implementazione del metodo, non è necessario nemmeno l’uso delle parentesi graffe. Ad esempio, l’interfaccia anonima Interface1 sopra può essere istanziata utilizzando una lambda come segue:

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

Le espressioni lambda sono un modo per creare facilmente classi anonime di interfacce funzionali. Non ci sono vantaggi in fase di esecuzione nell’uso delle espressioni lambda, quindi le utilizzerò con cautela perché non mi dispiace scrivere qualche riga di codice in più.

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.

Puoi leggere il tutorial completo su Tutorial sulle Espressioni Lambda di Java 8.

4. API Stream di Java per Operazioni su Dati in Blocco su Collezioni

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.

L’interfaccia Collection è stata estesa con i metodi predefiniti stream() e parallelStream() per ottenere lo Stream per l’esecuzione sequenziale e parallela. Vediamo il loro utilizzo con un semplice esempio.

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 sequenziale
		Stream<Integer> sequentialStream = myList.stream();
		
		//stream parallelo
		Stream<Integer> parallelStream = myList.parallelStream();
		
		//utilizzo di lambda con Stream API, esempio di filtro
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		//utilizzo di lambda in 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 esegui il codice di esempio sopra, otterrai un output simile a questo:

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

Nota che i valori del processo parallelo non sono in ordine, quindi il processamento parallelo sarà molto utile quando si lavora con collezioni molto grandi.

Coprire tutto riguardo all’API Stream non è possibile in questo post, puoi leggere tutto sull’API Stream su Tutorial di Esempi di Java 8 Stream API.

5. API Java Time

È sempre stato difficile lavorare con Date, Tempo e Fusi Orari in Java. Non c’era un approccio standard o un’API in Java per le date e il tempo. Uno dei bei miglioramenti in Java 8 è il pacchetto java.time che semplificherà il processo di lavoro con il tempo in Java.

Solo guardando i pacchetti dell’API Time di Java, posso intuire che saranno molto facili da usare. Ha alcuni sotto-pacchetti come java.time.format che fornisce classi per stampare e analizzare date e tempi e java.time.zone che fornisce supporto per i fusi orari e le loro regole.

La nuova API Time preferisce gli enum rispetto alle costanti intere per i mesi e i giorni della settimana. Una delle classi utili è DateTimeFormatter per convertire gli oggetti DateTime in stringhe. Per un tutorial completo, vai su Tutorial di Esempi dell’API Java Date Time.

6. Miglioramenti dell’API delle Collezioni

Abbiamo già visto il metodo forEach() e l’API Stream per le collezioni. Alcuni nuovi metodi aggiunti nell’API Collection sono:

  • Iterator metodo predefinito forEachRemaining(Consumer action) per eseguire l’azione data per ciascun elemento rimanente fino a quando tutti gli elementi sono stati elaborati o l’azione genera un’eccezione.
  • Collection metodo predefinito removeIf(Predicate filter) per rimuovere tutti gli elementi di questa collezione che soddisfano il predicato dato.
  • Collection metodo spliterator() che restituisce un’istanza di Spliterator che può essere utilizzata per attraversare gli elementi in sequenza o in parallelo.
  • Map replaceAll(), compute(), merge() metodi.
  • Miglioramento delle prestazioni per la classe HashMap con collisioni di chiavi

7. Miglioramenti dell’API di concorrenza

Alcuni importanti miglioramenti dell’API concorrente sono:

  • ConcurrentHashMap metodi compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() e search().
  • CompletableFuture che può essere esplicitamente completato (impostando il suo valore e stato).
  • Executors metodo newWorkStealingPool() per creare un pool di thread che ruba lavoro utilizzando tutti i processori disponibili come livello di parallelismo obiettivo.

8. Miglioramenti all’IO di Java

Alcuni miglioramenti dell’IO che conosco sono:

  • Files.list(Path dir) che restituisce uno Stream popolato in modo pigro, gli elementi dei quali sono le voci nella directory.
  • Files.lines(Path path) che legge tutte le righe da un file come uno Stream.
  • Files.find() che restituisce uno Stream popolato in modo pigro con Path cercando file in un albero di file radicato in un dato file di partenza.
  • BufferedReader.lines() che restituisce uno Stream, gli elementi dei quali sono righe lette da questo BufferedReader.

Miglioramenti vari all’API di base di Java 8

Alcuni miglioramenti vari dell’API che potrebbero essere utili sono:

  1. Metodo statico ThreadLocal conInitial(Supplier supplier) per creare istanze facilmente.
  2. L’interfaccia Comparator è stata estesa con molti metodi predefiniti e statici per l’ordinamento naturale, l’ordinamento inverso, ecc.
  3. Metodi min(), max() e sum() nelle classi wrapper Integer, Long e Double.
  4. I metodi logicalAnd(), logicalOr() e logicalXor() nella classe Boolean.
  5. Il metodo stream() di ZipFile per ottenere uno Stream ordinato sulle voci del file ZIP. Le voci appaiono nello Stream nell’ordine in cui compaiono nel directory centrale del file ZIP.
  6. Svariati metodi di utilità nella classe Math.
  7. Il comando jjs è stato aggiunto per invocare l’engine Nashorn.
  8. Il comando jdeps è stato aggiunto per analizzare i file di classe
  9. Il ponte JDBC-ODBC è stato rimosso.
  10. Lo spazio di memoria PermGen è stato rimosso

Questo è tutto per le caratteristiche di Java 8 con esempi di programmi. Se ho saltato delle caratteristiche importanti di Java 8, per favore fatemelo sapere attraverso i commenti.

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