Guida a EnumSet

1. Introduzione

In questo tutorial, esploreremo la raccolta EnumSet dal pacchetto java.util e discuteremo le sue peculiarità.

Mostreremo prima le caratteristiche principali della collezione e successivamente esamineremo gli interni della classe per comprenderne i vantaggi.

Infine, tratteremo le principali operazioni che fornisce e implementeremo alcuni esempi di base.

2. Che cos'è un EnumSet

Un EnumSet è una raccolta di Set specializzata per lavorare con le classi enum . Implementa l' interfaccia Set e si estende da AbstractSet :

Anche se AbstractSet e AbstractCollection forniscono implementazioni per quasi tutti i metodi delle interfacce Set e Collection , EnumSet sovrascrive la maggior parte di essi.

Quando pensiamo di utilizzare un EnumSet dobbiamo prendere in considerazione alcuni punti importanti:

  • Può contenere solo valori enum e tutti i valori devono appartenere alla stessa enum
  • Non consente di aggiungere valori null , generando una NullPointerException nel tentativo di farlo
  • Non è thread-safe , quindi è necessario sincronizzarlo esternamente se necessario
  • Gli elementi vengono archiviati seguendo l'ordine in cui sono dichiarati nell'enum
  • Utilizza un iteratore a prova di errore che funziona su una copia, quindi non genera un'eccezione ConcurrentModificationException se la raccolta viene modificata durante l'iterazione su di essa

3. Perché utilizzare EnumSet

Come regola pratica, EnumSet dovrebbe sempre essere preferito rispetto a qualsiasi altra implementazione di Set quando memorizziamo valori enum .

Nelle prossime sezioni vedremo cosa rende questa raccolta migliore di altre. Per fare ciò, mostreremo brevemente le parti interne della classe per ottenere una migliore comprensione.

3.1. Dettagli di implementazione

EnumSet è una classe astratta pubblica che contiene più metodi factory statici che ci consentono di creare istanze. Il JDK fornisce 2 diverse implementazioni - sono private del pacchetto e supportate da un vettore di bit:

  • RegularEnumSet e
  • JumboEnumSet

RegularEnumSet utilizza un singolo long per rappresentare il vettore di bit. Ogni bitdell'elemento long rappresenta un valore dell'enumerazione . Il valore i-esimo dell'enumerazione verrà memorizzato nell'i-esimo bit, quindi è abbastanza facile sapere se un valore è presente o meno. Poiché long è un tipo di dati a 64 bit, questa implementazione può memorizzare fino a 64 elementi.

D'altra parte, JumboEnumSet utilizza un array di elementi lunghi come vettore di bit. Ciò consente a questa implementazione di memorizzare più di 64 elementi. Funziona più o meno come RegularEnumSet ma esegue alcuni calcoli aggiuntivi per trovare l'indice dell'array in cui è memorizzato il valore.

Non sorprende che il primo elemento lungo dell'array memorizzerà i 64 primi valori di enum , il secondo elemento i successivi 64 e così via.

I metodi factory EnumSet creano istanze di un'implementazione o di un'altra a seconda del numero di elementi dell'enum :

if (universe.length <= 64) return new RegularEnumSet(elementType, universe); else return new JumboEnumSet(elementType, universe);

Tieni presente che prende in considerazione solo la dimensione della classe enum , non il numero di elementi che verranno archiviati nella raccolta.

3.2. Vantaggi dall'utilizzo di un EnumSet

A causa dell'implementazione di un EnumSet che abbiamo descritto sopra, tutti i metodi in un EnumSet vengono implementati utilizzando operazioni aritmetiche bit per bit. Questi calcoli sono molto veloci e quindi tutte le operazioni di base vengono eseguite in un tempo costante.

Se confrontiamo EnumSet con altre implementazioni di Set come HashSet , la prima è solitamente più veloce perché i valori sono memorizzati in un ordine prevedibile e deve essere esaminato solo un bit per ogni calcolo. A differenza di HashSet , non è necessario calcolare il codice hash per trovare il bucket giusto.

Inoltre, a causa della natura dei vettori di bit, un EnumSet è molto compatto ed efficiente. Pertanto, utilizza meno memoria, con tutti i vantaggi che porta.

4. Operazioni principali

La maggior parte dei metodi di un EnumSet funziona come qualsiasi altro Set , ad eccezione dei metodi per creare istanze.

Nelle prossime sezioni, mostreremo in dettaglio tutti i metodi di creazione e tratteremo brevemente il resto dei metodi.

Nei nostri esempi, lavoreremo con un'enumerazione Color :

public enum Color { RED, YELLOW, GREEN, BLUE, BLACK, WHITE }

4.1. Metodi creazionali

I metodi più semplici per creare un EnumSet sono allOf () e noneOf () . In questo modo possiamo creare facilmente un EnumSet contenente tutti gli elementi del nostro Color enum:

EnumSet.allOf(Color.class);

Allo stesso modo, possiamo usare noneOf () per fare il contrario e creare una raccolta vuota di Color :

EnumSet.noneOf(Color.class);

Se vogliamo creare un EnumSet con un sottoinsieme degli elementi enum possiamo usare i metodi di overload of () . È importante distinguere tra i metodi con un numero fisso di parametri fino a 5 diversi e quello che utilizza varargs :

Il Javadoc afferma che le prestazioni della versione varargs possono essere più lente delle altre a causa della creazione dell'array. Pertanto, dovremmo usarlo solo se inizialmente dobbiamo aggiungere più di 5 elementi.

Un altro modo per creare un sottoinsieme di un'enumerazione è usare il metodo range () :

EnumSet.range(Color.YELLOW, Color.BLUE);

Nell'esempio sopra, EnumSet contiene tutti gli elementi da Yellow a Blue. Seguono l'ordine definito nell'enumerazione :

[YELLOW, GREEN, BLUE]

Si noti che include sia il primo che l'ultimo elemento specificato.

Un altro utile metodo factory è il complementOf () che ci permette di escludere gli elementi passati come parametri . Creiamo un EnumSet con tutti gli elementi Color tranne il bianco e nero:

EnumSet.complementOf(EnumSet.of(Color.BLACK, Color.WHITE));

Se stampiamo questa raccolta possiamo vedere che contiene tutti gli altri elementi:

[RED, YELLOW, GREEN, BLUE]

Infine, possiamo creare un EnumSet copiando tutti gli elementi da un altro EnumSet :

EnumSet.copyOf(EnumSet.of(Color.BLACK, Color.WHITE));

Internamente, chiama il metodo clone .

Inoltre, possiamo anche copiare tutti gli elementi da qualsiasi Collection che contiene elementi enum . Usiamolo per copiare tutti gli elementi di una lista:

List colorsList = new ArrayList(); colorsList.add(Color.RED); EnumSet listCopy = EnumSet.copyOf(colorsList);

In questo caso, listCopy contiene solo il colore rosso.

4.2. Altre operazioni

Il resto delle operazioni funzionano esattamente allo stesso modo di qualsiasi altra implementazione di Set e non c'è differenza nel modo in cui usarle.

Pertanto, possiamo facilmente creare un EnumSet vuoto e aggiungere alcuni elementi:

EnumSet set = EnumSet.noneOf(Color.class); set.add(Color.RED); set.add(Color.YELLOW)

Controlla se la raccolta contiene un elemento specifico:

set.contains(Color.RED);

Itera sugli elementi:

set.forEach(System.out::println);

O semplicemente rimuovi elementi:

set.remove(Color.RED);

Questa, ovviamente, tra tutte le altre operazioni supportate da un Set .

5. conclusione

In questo articolo, abbiamo mostrato le caratteristiche principali di EnumSet , la sua implementazione interna e come possiamo trarre vantaggio dal suo utilizzo.

Abbiamo anche coperto i metodi principali che offre e implementato alcuni esempi per mostrare come possiamo usarli.

Come sempre, il codice sorgente completo degli esempi è disponibile su GitHub.