Guía de EnumSet

1. Introducción

En este tutorial, exploraremos la colección EnumSet del paquete java.util y discutiremos sus peculiaridades.

Primero mostraremos las características principales de la colección y luego, repasaremos los aspectos internos de la clase para comprender sus beneficios.

Finalmente, cubriremos las principales operaciones que proporciona e implementaremos algunos ejemplos básicos.

2. ¿Qué es un EnumSet?

Un EnumSet es una colección de conjuntos especializada para trabajar con clases de enumeración . Implementa la interfaz Set y se extiende desde AbstractSet :

Aunque AbstractSet y AbstractCollection proporcionan implementaciones para casi todos los métodos de las interfaces Set y Collection , EnumSet anula la mayoría de ellos.

Cuando planeamos usar un EnumSet, debemos tener en cuenta algunos puntos importantes:

  • Puede contener solo valores de enumeración y todos los valores deben pertenecer a la misma enumeración
  • No permite agregar valores nulos , lanzando una NullPointerException en un intento de hacerlo
  • No es seguro para subprocesos , por lo que debemos sincronizarlo externamente si es necesario
  • Los elementos se almacenan siguiendo el orden en el que se declaran en la enumeración
  • Utiliza un iterador a prueba de fallas que funciona en una copia, por lo que no arrojará una ConcurrentModificationException si la colección se modifica al iterar sobre ella

3. Por qué usar EnumSet

Como regla general, EnumSet siempre debe preferirse a cualquier otra implementación de Set cuando estamos almacenando valores de enumeración .

En las siguientes secciones, veremos qué hace que esta colección sea mejor que otras. Para ello, mostraremos brevemente los aspectos internos de la clase para comprender mejor.

3.1. Detalles de implementacion

EnumSet es una clase abstracta pública que contiene múltiples métodos de fábrica estáticos que nos permiten crear instancias. El JDK proporciona 2 implementaciones diferentes: son paquetes privados y están respaldados por un vector de bits:

  • RegularEnumSet y
  • JumboEnumSet

RegularEnumSet usa un solo long para representar el vector de bits. Cada bit delelemento long representa un valor de la enumeración . El i-ésimo valor de la enumeración se almacenará en el i-ésimo bit, por lo que es bastante fácil saber si un valor está presente o no. Dado que long es un tipo de datos de 64 bits, esta implementación puede almacenar hasta 64 elementos.

Por otro lado, JumboEnumSet usa una matriz de elementos largos como vector de bits. Esto permite que esta implementación almacene más de 64 elementos. Funciona de forma muy parecida a RegularEnumSet, pero hace algunos cálculos adicionales para encontrar el índice de matriz donde se almacena el valor.

Como era de esperar, el primer elemento largo de la matriz almacenará los primeros 64 valores de la enumeración , el segundo elemento los siguientes 64, y así sucesivamente.

Los métodos de fábrica EnumSet crean instancias de una implementación u otra dependiendo de la cantidad de elementos de la enumeración :

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

Tenga en cuenta que solo tiene en cuenta el tamaño de la clase enum , no la cantidad de elementos que se almacenarán en la colección.

3.2. Beneficios de usar un EnumSet

Debido a la implementación de un EnumSet que hemos descrito anteriormente, todos los métodos en un EnumSet se implementan usando operaciones aritméticas bit a bit. Estos cálculos son muy rápidos y por lo tanto todas las operaciones básicas se ejecutan en un tiempo constante.

Si comparamos EnumSet con otras implementaciones de Set como HashSet , la primera suele ser más rápida porque los valores se almacenan en un orden predecible y solo se necesita examinar un bit para cada cálculo. A diferencia de HashSet , no es necesario calcular el código hash para encontrar el depósito correcto.

Además, debido a la naturaleza de los vectores de bits, un EnumSet es muy compacto y eficiente. Por tanto, utiliza menos memoria, con todos los beneficios que aporta.

4. Operaciones principales

La mayoría de los métodos de un EnumSet funcionan como cualquier otro Set , con la excepción de los métodos para crear instancias.

En las siguientes secciones, mostraremos en detalle todos los métodos de creación y cubriremos brevemente el resto de métodos.

En nuestros ejemplos, trabajaremos con una enumeración de Color :

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

4.1. Métodos de creación

Los métodos más simples para crear un EnumSet son allOf () y noneOf () . De esta manera podemos crear fácilmente un EnumSet que contenga todos los elementos de nuestra enumeración de Color :

EnumSet.allOf(Color.class);

Asimismo, podemos usar noneOf () para hacer lo contrario y crear una colección vacía de Color :

EnumSet.noneOf(Color.class);

Si queremos crear un EnumSet con un subconjunto de los elementos de enum , podemos usar los métodos sobrecargados de () . Es importante diferenciar entre los métodos con un número fijo de parámetros hasta 5 diferentes y el que usa varargs :

El Javadoc afirma que el rendimiento de la versión de varargs puede ser más lento que el de los demás debido a la creación de la matriz. Por lo tanto, debemos usarlo solo si inicialmente necesitamos agregar más de 5 elementos.

Otra forma de crear un subconjunto de una enumeración es mediante el método range () :

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

En el ejemplo anterior, EnumSet contiene todos los elementos de amarillo a azul. Siguen el orden definido en la enumeración :

[YELLOW, GREEN, BLUE]

Observe que incluye tanto el primer como el último elemento especificado.

Otro método de fábrica útil es el complementoOf () que nos permite excluir los elementos pasados ​​como parámetros . Creemos un EnumSet con todos los elementos de Color excepto el blanco y negro:

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

Si imprimimos esta colección podemos ver que contiene todos los demás elementos:

[RED, YELLOW, GREEN, BLUE]

Finalmente, podemos crear un EnumSet copiando todos los elementos de otro EnumSet :

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

Internamente, llama al método de clonación .

Además, también podemos copiar todos los elementos de cualquier colección que contenga elementos de enumeración . Usémoslo para copiar todos los elementos de una lista:

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

En este caso, listCopy solo contiene el color rojo.

4.2. Otras operaciones

El resto de las operaciones funcionan exactamente de la misma manera que cualquier otra implementación de Set y no hay diferencia en cómo usarlas.

Por lo tanto, podemos crear fácilmente un EnumSet vacío y agregar algunos elementos:

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

Compruebe si la colección contiene un elemento específico:

set.contains(Color.RED);

Itera sobre los elementos:

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

O simplemente elimine elementos:

set.remove(Color.RED);

Esto, por supuesto, entre todas las demás operaciones que admite un Conjunto .

5. Conclusión

En este artículo, mostramos las características principales de EnumSet , su implementación interna y cómo podemos beneficiarnos de su uso.

También cubrimos los principales métodos que ofrece e implementamos algunos ejemplos para mostrar cómo podemos usarlos.

Como siempre, el código fuente completo de los ejemplos está disponible en GitHub.