Lavorare con le mappe utilizzando i flussi

1. Introduzione

In questo tutorial, discuteremo alcuni esempi di come utilizzare Java Stream sper lavorare con Map s. Vale la pena notare che alcuni di questi esercizi potrebbero essere risolti utilizzando una struttura dati mappa bidirezionale , ma qui ci interessa un approccio funzionale.

Innanzitutto, spieghiamo l'idea di base che utilizzeremo per lavorare con Maps e Stream s. Quindi presentiamo un paio di problemi diversi relativi a Maps e alle loro soluzioni concrete utilizzando Stream s.

2. Idea di base

La cosa principale da notare è che gli Stream sono sequenze di elementi che possono essere facilmente ottenuti da una Collection .

Le mappe hanno una struttura diversa, con una mappatura dalle chiavi ai valori, senza sequenza. Ciò non significa che non possiamo convertire una struttura Map in sequenze diverse che ci permettono di lavorare in modo naturale con l'API Stream.

Vediamo come ottenere diverse raccolte da una mappa , che possiamo quindi ruotare in uno stream :

Map someMap = new HashMap();

Possiamo ottenere una serie di coppie chiave-valore:

Set
    
      entries = someMap.entrySet();
    

Possiamo anche ottenere il set di chiavi associato alla mappa :

Set keySet = someMap.keySet();

Oppure potremmo lavorare direttamente con l'insieme di valori:

Collection values = someMap.values();

Ognuno di questi ci fornisce un punto di ingresso per elaborare tali raccolte ottenendo flussi da esse:

Stream
    
      entriesStream = entries.stream(); Stream valuesStream = values.stream(); Stream keysStream = keySet.stream();
    

3. Ottenere una mappa 's chiavi utilizzando flusso s

3.1. Dati in ingresso

Supponiamo di avere una mappa :

Map books = new HashMap(); books.put( "978-0201633610", "Design patterns : elements of reusable object-oriented software"); books.put( "978-1617291999", "Java 8 in Action: Lambdas, Streams, and functional-style programming"); books.put("978-0134685991", "Effective Java");

Ci interessa trovare l'ISBN per il libro dal titolo "Effective Java".

3.2. Recupero di una corrispondenza

Poiché il titolo del libro non può esistere nella nostra mappa , vogliamo essere in grado di indicare che non vi è alcun ISBN associato. Possiamo usare un Opzionale per esprimere che :

Supponiamo per questo esempio di essere interessati a qualsiasi chiave per un libro che corrisponda a quel titolo:

Optional optionalIsbn = books.entrySet().stream() .filter(e -> "Effective Java".equals(e.getValue())) .map(Map.Entry::getKey) .findFirst(); assertEquals("978-0134685991", optionalIsbn.get());

Analizziamo il codice. Per prima cosa, otteniamo l' entrySet dalla mappa , come abbiamo visto in precedenza.

Vogliamo considerare solo le voci con "Java effettivo" come titolo, quindi la prima operazione intermedia sarà un filtro.

Non siamo interessati all'intera voce della mappa , ma alla chiave di ciascuna voce. Quindi, la successiva operazione intermedia concatenata fa proprio questo: è un'operazione di mappa che genererà un nuovo flusso come output che conterrà solo le chiavi per le voci che corrispondono al titolo che stavamo cercando.

Poiché vogliamo solo un risultato, possiamo applicare l' operazione del terminale findFirst () , che fornirà il valore iniziale nello Stream come oggetto opzionale .

Vediamo un caso in cui non esiste un titolo:

Optional optionalIsbn = books.entrySet().stream() .filter(e -> "Non Existent Title".equals(e.getValue())) .map(Map.Entry::getKey).findFirst(); assertEquals(false, optionalIsbn.isPresent());

3.3. Recupero di più risultati

Cambiamo ora il problema per vedere come possiamo gestire la restituzione di più risultati invece di uno.

Per avere più risultati restituiti, aggiungiamo il seguente libro alla nostra mappa :

books.put("978-0321356680", "Effective Java: Second Edition"); 

Quindi ora, se cerchiamo tutti i libri che iniziano con "Java efficace", otterremo più di un risultato:

List isbnCodes = books.entrySet().stream() .filter(e -> e.getValue().startsWith("Effective Java")) .map(Map.Entry::getKey) .collect(Collectors.toList()); assertTrue(isbnCodes.contains("978-0321356680")); assertTrue(isbnCodes.contains("978-0134685991"));

Quello che abbiamo fatto in questo caso è sostituire la condizione del filtro per verificare se il valore nella mappa inizia con "Java effettivo" invece di confrontare per l' uguaglianza di stringa .

Questa volta, abbiamo raccogliere i risultati - invece di prendere il primo - mettendo le partite in un elenco .

4. Ottenere una mappa 's Valori Uso flusso s

Ora, concentriamoci su un problema diverso con le mappe: invece di ottenere codici ISBN basati sui titoli , proveremo a ottenere titoli basati sugli ISBN.

Usiamo la mappa originale . Vogliamo trovare titoli i cui codici ISBN iniziano con "978-0".

List titles = books.entrySet().stream() .filter(e -> e.getKey().startsWith("978-0")) .map(Map.Entry::getValue) .collect(Collectors.toList()); assertEquals(2, titles.size()); assertTrue(titles.contains( "Design patterns : elements of reusable object-oriented software")); assertTrue(titles.contains("Effective Java"));

Questa soluzione è simile alle soluzioni alla nostra precedente serie di problemi: trasmettiamo il set di voci, quindi filtriamo, mappiamo e raccogliamo.

E come prima, se volessimo restituire solo la prima corrispondenza, potremmo dopo il metodo map chiamare il metodo findFirst () invece di raccogliere tutti i risultati in un List .

5. conclusione

Abbiamo mostrato come elaborare una mappa in modo funzionale .

In particolare, abbiamo visto che una volta che si passa all'utilizzo delle raccolte associate a Map s, l'elaborazione tramite Stream s diventa molto più semplice e intuitiva.

E, naturalmente, tutti gli esempi possono essere trovati nel progetto GitHub.