Optional

  • java.util.Optional è un contenitore di un riferimento che potrebbe essere o non essere null
  • Un metodo può restituire un Optional invece di restituire un riferimento potenzialmente null
  • Serve a evitare i NullPointerException

Creare e verificare u java.util.Optional

  • Un Optional senza riferimento
Optional.empty()
  • Un Optional non nullo
Optional.of("dsvdsds");
  • Un Optional di riferimento che può essere nullo
Optional<String> optional == Optional.ofNullable(s);
  • Controllo della presenza di un valore non null
Optional.empty().isPresent() == false
Optional.of("dsvdsds").isPresent() == true

Ottenere il valore java.util.Optional

  • Mediante orElse
Optional<String> op = Optional.of("eccomi")
op.orElse("fallback"); // "eccomi"
Optional.empty().orElse("fallback"); // "fallback"
  • Mediante ifPresent o ifPresentOrElse
op.ifPresent(System.out::println); // "eccomi"
  • Da non usare
optional.get(); // valore o solleva eccezione se non presente

Stream

  • Una nuova interfaccia di java.util.Stream
  • Rappresenta una sequenza di elementi su cui possono essere effettuare una o piĂą operazioni
  • Supporta operazioni sequenziali e parallele
  • Uno stream viene creato a partire da una sorgente di dati, per esempio una java.util.Collection
  • Al contrario delle Collection, uno Stream non memorizza nĂ© modifica i dati della sorgente, ma opera su di essi

Operazioni intermedie e terminali

  • Le operazioni possono essere terminali o intermedie
    • Intermedie: restituiscono un altro stream su cui continuare a lavorare
    • Terminali: restituiscono il tipo atteso

  • Una volta che uno stream è stato consumato (operazione terminale), non può essere riutilizzato
  • Builder pattern : si impostano una serie di operazioni per configurare e infine si costruisce l’oggetto

Metodi principali dell’interfaccia java.util.stream.Stream<T>

Operazioni intermedie e terminali

  • Una volta che uno stream è stato consumato, non può essere riutilizzato
  • Comportamento pigro: le operazioni intermedie non vengono eseguite immediatamente, ma solo quando si richiede l’esecuzione di un’operazione termianale
  • Le operazioni possono essere:
    • senza stato: l’elaborazione dei vari elementi può procedere in modo indipendente
    • con stato: l’elaborazione di un elemento potrebbe dipendere da quella di altri elementi

Come ottenere uno stream

  • Direttamente dai dati: con il metodo statico generico Stream.of(elenco di dati di un certo tipo)
  • In Java 8 l’interfaccia Collection è stata estesa per includere die nuovi metodi di default
    • default Stream <E>stream() restituisce un nuovo stream sequenziale
    • default Stream<E> parallelStream() restituisce un nuovo stream parallelo, se possibile (altrimenti restituisce uno stream sequenziale)
  • E’ possibile ottenere uno stream anche per un array, con il metodo statico Stream<T> Arrays.stream(T[ ] array)
  • E’ possibile ottenere uno stream di righe di testo da BufferedReader.lines() oppure da Files.lines(Path)
  • E’ possibile ottenere uno stream di righe anche da String.lines

Stream vs Collection

  • Lo stream permette di utilizzare uno stile dichiarativo
    • Iterazione interna sui dati
  • La collection impone l’utilizzo di uno stile imperativo
    • Iterazione esterna sui dati (tranne con forEach)
  • Lo stream si focalizza sulle operazioni di alto livello da eseguire (mattoncini o building blocks) eventualmente anche in parallelo, senza specificare come verranno eseguite
  • Stream: dichiarativo, componibile, parallelizzabile

Metodi min e max

  • I metodi min e max restituiscono rispettivamente il minimo e il massimo di uno stream sotto forma di Optional
  • Prendono in input un Comparator sul tipo degli elementi dello stream
List<Integer> p = Arrays.asList(2, 3, 4, 5, 6, 7);
Optional<Integer> max = p.stream().max(Integer::compare);
//se c'è, restituisce il massimo, altrimenti -1
System.out.println(max.orElse(-1));

Collect

  • E’ un’operazioe terminale che permette di raccogliere gli elementi dello stream in un qualche oggetto
  • Ad esempio, per ottenere la lista dei prezzi ivati:
List<Integer>ivaEsclusa = Arrays.asList(10, 20, 30); 
//java 7
List<Double> I = new ArrayList<>();
//java 8
List<Double> I = ivaEsclusa.stream().map(p -> p*1.22)
									.collect(Collectors.toList());
  • Esempio: creare una stringa che concatena stringhe in un elenco, rese maiuscole e separate da virgola
List<String> l = Arrays.asList("RoMa", "milano", "Torino");
String s = "";
 
// in Java 7:
for (String e : l) s += e.toUpperCase()+", ";
s = s.substring(0, s.length()-2);
 
// in Java 8:
s = l.stream().map(e -> e.toUpperCase())
.collect(Collectors.joining(", "));
  • Esempio: trasformare una lista di stringhe in una lista delle lunghezze delle stesse
List<String> words = Arrays.asList("Oracle", "Java",
"Magazine");
List<Integer> wordLengths = 
						words.stream()
						.map(String::length)
						.collect(toList());

Collectors

  • Ricettario che ci permette di ridurre gli elementi di uno stream per raccoglierli in qualche modo
  • Per rendere il codice piĂą leggibile: import static java.util.stream.Collectors
    • In questo modo possiamo scrivere il nome del metodo senza anteporre Collectors (ex. toList() invece di Collectors.toList())

Collectors: riduzioni a singolo elemento

  • counting() restituisce il numero di elementi nello stream (risultato di tipo long)
List<Integer> l = Arrays.asList(2, 3, 5, 6);
// k == 2
long k = l.stream().filter(x->x<5).collect(Collectors.counting()));
  • maxBy/minBy(comparator) restituisce un Optional con il massimo/minimo valore
// max contiene 6
Optional<Integer> max = l.stream().collect(maxBy(Integer::compareTo));
  • summingInt(lambda che mappa ogni elemento a intero)/averagingInt, summingDouble, averagingDouble

Collectors.toMap: riduzione a una mappa

  • toMap prende input fino a 4 argomenti:
    • la funzione per mappare l’oggetto dello stream nella chiave della mappa
    • la funzione per mappare l’oggetto dello stream nel valore della mappa
    • opzionale: la funzione da utilizzare per unire il valore preesistente nella mappa a fronte della chiave con il valore associato all’oggetto dalla seconda funzione (non devono trovarsi due chiavi uguali o si ottiene un’eccezione IllegalStateException)
    • opzionale: il Supplier che crea la mappa
Map<Integer, String> map = persons
.stream()
.collect(Collectors.toMap(
Person::getAge,
Person::getName,
(name1, name2) -> name1 + ";" + name2));