Guida a Guava Multiset

1. Panoramica

In questo tutorial, esploreremo una delle collezioni Guava - Multiset . Come un java.util.Set , consente l'archiviazione e il recupero efficienti degli articoli senza un ordine garantito.

Tuttavia, a differenza di un set , consente più occorrenze dello stesso elemento monitorando il conteggio di ogni elemento univoco che contiene.

2. Dipendenza da Maven

Innanzitutto, aggiungiamo la dipendenza guava :

 com.google.guava guava 29.0-jre 

3. Utilizzo di Multiset

Consideriamo una libreria che ha più copie di libri diversi. Potremmo voler eseguire operazioni come aggiungere una copia, ottenere il numero di copie e rimuovere una copia quando viene venduta. Poiché un set non consente più ricorrenze dello stesso elemento, non può gestire questo requisito.

Iniziamo aggiungendo copie del titolo di un libro. Il Multiset dovrebbe restituire che il titolo esiste e fornirci il conteggio corretto :

Multiset bookStore = HashMultiset.create(); bookStore.add("Potter"); bookStore.add("Potter"); bookStore.add("Potter"); assertThat(bookStore.contains("Potter")).isTrue(); assertThat(bookStore.count("Potter")).isEqualTo(3);

Ora rimuoviamo una copia. Ci aspettiamo che il conteggio venga aggiornato di conseguenza:

bookStore.remove("Potter"); assertThat(bookStore.contains("Potter")).isTrue(); assertThat(bookStore.count("Potter")).isEqualTo(2);

E in realtà, possiamo semplicemente impostare il conteggio invece di eseguire varie operazioni di aggiunta:

bookStore.setCount("Potter", 50); assertThat(bookStore.count("Potter")).isEqualTo(50);

Multiset convalida il valore di conteggio . Se lo impostiamo su negativo, viene generata un'eccezione IllegalArgumentException :

assertThatThrownBy(() -> bookStore.setCount("Potter", -1)) .isInstanceOf(IllegalArgumentException.class);

4. Confronto con la mappa

Senza accesso a Multiset , potremmo ottenere tutte le operazioni sopra implementando la nostra logica utilizzando java.util.Map:

Map bookStore = new HashMap(); // adding 3 copies bookStore.put("Potter", 3); assertThat(bookStore.containsKey("Potter")).isTrue(); assertThat(bookStore.get("Potter")).isEqualTo(3); // removing 1 copy bookStore.put("Potter", 2); assertThat(bookStore.get("Potter")).isEqualTo(2);

Quando vogliamo aggiungere o rimuovere una copia utilizzando una mappa , dobbiamo ricordare il conteggio corrente e regolarlo di conseguenza. Abbiamo anche bisogno di implementare questa logica nel nostro codice chiamante ogni volta o costruire la nostra libreria per questo scopo. Il nostro codice dovrebbe anche controllare l' argomento value . Se non stiamo attenti, potremmo facilmente impostare il valore su null o negativo anche se entrambi i valori non sono validi:

bookStore.put("Potter", null); assertThat(bookStore.containsKey("Potter")).isTrue(); bookStore.put("Potter", -1); assertThat(bookStore.containsKey("Potter")).isTrue(); 

Come possiamo vedere, è molto più comodo usare Multiset invece di Map .

5. Concorrenza

Quando vogliamo usare Multiset in un ambiente simultaneo, possiamo usare ConcurrentHashMultiset , che è un'implementazione Multiset thread-safe .

Dobbiamo notare che essere thread-safe non garantisce la coerenza, tuttavia. L'uso dei metodi aggiungi o rimuovi funzionerà bene in un ambiente multi-thread, ma cosa succede se diversi thread chiamano il metodo setCount ?

Se usiamo il metodo setCount , il risultato finale dipenderà dall'ordine di esecuzione tra i thread , che non può essere necessariamente previsto. I metodi di aggiunta e rimozione sono incrementali e ConcurrentHashMultiset è in grado di proteggere il loro comportamento. L'impostazione diretta del conteggio non è incrementale e quindi può causare risultati imprevisti se utilizzata contemporaneamente.

Tuttavia, c'è un altro aspetto del metodo setCount che aggiorna il conteggio solo se il suo valore corrente corrisponde all'argomento passato. Il metodo restituisce true se l'operazione è riuscita, una forma di blocco ottimistico:

Multiset bookStore = HashMultiset.create(); // updates the count to 2 if current count is 0 assertThat(bookStore.setCount("Potter", 0, 2)).isTrue(); // updates the count to 5 if the current value is 50 assertThat(bookStore.setCount("Potter", 50, 5)).isFalse();

Se vogliamo utilizzare il metodo setCount nel codice simultaneo, dovremmo usare la versione sopra per garantire la coerenza. Un client multi-thread potrebbe eseguire un nuovo tentativo se la modifica del conteggio non è riuscita.

6. Conclusione

In questo breve tutorial, abbiamo discusso di quando e come usare un Multiset, lo abbiamo confrontato con una mappa standard e abbiamo esaminato il modo migliore per usarlo in un'applicazione simultanea.

Come sempre, il codice sorgente per gli esempi può essere trovato su GitHub.