Перейти к основному содержимому

Руководство по EnumSet

· 6 мин. чтения

**1. Введение**

В этом руководстве мы рассмотрим коллекцию EnumSet из пакета java.util и обсудим ее особенности.

Сначала мы покажем основные возможности коллекции, а после этого пройдемся по внутренностям класса, чтобы понять его преимущества.

Наконец, мы рассмотрим основные операции, которые он предоставляет, и реализуем несколько основных примеров.

2. Что такое EnumSet

EnumSet — это специализированная коллекция Set для работы с перечислимыми классами . Он реализует интерфейс Set и расширяется от AbstractSet :

./de754e2d306aff66cca0e56059bf9778.jpg

Несмотря на то, что AbstractSet и AbstractCollection предоставляют реализации почти для всех методов интерфейсов Set и Collection , EnumSet переопределяет большинство из них.

Когда мы планируем использовать EnumSet , мы должны учитывать некоторые важные моменты:

  • Он может содержать только значения перечисления , и все значения должны принадлежать одному и тому же перечислению .
  • Он не позволяет добавлять нулевые значения , вызывая исключение NullPointerException при попытке сделать это.
  • Это не потокобезопасно , поэтому нам нужно синхронизировать его извне, если это необходимо .
  • Элементы хранятся в том порядке, в котором они объявлены в перечислении .
  • Он использует отказоустойчивый итератор , который работает с копией, поэтому он не будет вызывать исключение ConcurrentModificationException , если коллекция будет изменена при итерации по ней.

3. Зачем использовать EnumSet

Как показывает опыт, EnumSet всегда должен быть предпочтительнее любой другой реализации Set , когда мы сохраняем значения перечисления .

В следующих разделах мы увидим, чем эта коллекция лучше других. Для этого мы кратко покажем внутренности класса, чтобы лучше понять его.

3.1. Детали реализации

EnumSet — это общедоступный абстрактный класс, который содержит несколько статических фабричных методов, которые позволяют нам создавать экземпляры. JDK предоставляет 2 разные реализации — закрытые для пакетов и поддерживаемые битовым вектором:

  • RegularEnumSet и
  • ДжамбоЭнумсет

RegularEnumSet использует один тип long для представления битового вектора. Каждый бит длинного элемента представляет собой значение перечисления . i-е значение перечисления будет сохранено в i-м бите, поэтому довольно легко узнать, присутствует ли значение или нет. Поскольку тип данных long является 64-битным, эта реализация может хранить до 64 элементов.

С другой стороны, JumboEnumSet использует массив длинных элементов в качестве битового вектора. Это позволяет этой реализации хранить более 64 элементов. Он работает почти так же, как RegularEnumSet , но выполняет некоторые дополнительные вычисления, чтобы найти индекс массива, в котором хранится значение.

Неудивительно, что первый длинный элемент массива будет хранить 64 первых значения перечисления , второй элемент — следующие 64 и так далее.

Фабричные методы EnumSet создают экземпляры той или иной реализации в зависимости от количества элементов перечисления :

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

Имейте в виду, что он учитывает только размер класса перечисления , а не количество элементов, которые будут храниться в коллекции.

3.2. Преимущества использования EnumSet

Из-за описанной выше реализации EnumSet все методы в EnumSet реализованы с использованием арифметических побитовых операций. Эти вычисления выполняются очень быстро, поэтому все основные операции выполняются за постоянное время.

Если мы сравним EnumSet с другими реализациями Set , такими как HashSet , первая обычно быстрее, потому что значения хранятся в предсказуемом порядке и для каждого вычисления необходимо проверять только один бит. В отличие от HashSet , нет необходимости вычислять хэш -код , чтобы найти нужное ведро.

Более того, благодаря природе битовых векторов EnumSet очень компактен и эффективен. Следовательно, он использует меньше памяти со всеми преимуществами, которые он приносит.

4. Основные операции

Большинство методов EnumSet работают так же, как и любой другой набор , за исключением методов создания экземпляров.

В следующих разделах мы подробно покажем все методы создания и кратко рассмотрим остальные методы.

В наших примерах мы будем работать с перечислением Color : ``

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

4.1. Творческие методы

Наиболее простыми методами создания EnumSet являются allOf() и noneOf() . Таким образом, мы можем легко создать EnumSet , содержащий все элементы нашего перечисления Color :

EnumSet.allOf(Color.class);

Точно так же мы можем использовать noneOf() , чтобы сделать обратное и создать пустую коллекцию Color :

EnumSet.noneOf(Color.class);

Если мы хотим создать EnumSet с подмножеством элементов перечисления , мы можем использовать перегруженные методы of() . Важно различать методы с фиксированным количеством параметров до 5 разных и тот, который использует varargs :

./a4ff464a4852b796ecd992f45191f271.png

В Javadoc указано, что производительность версии с переменным числом аргументов может быть ниже, чем у других, из-за создания массива. Поэтому мы должны использовать его только в том случае, если нам изначально нужно добавить более 5 элементов.

Другой способ создать подмножество перечисления — использовать метод range() :

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

В приведенном выше примере EnumSet содержит все элементы от желтого до синего. Они следуют порядку, определенному в перечислении :

[YELLOW, GREEN, BLUE]

Обратите внимание, что он включает в себя как первый, так и последний указанные элементы.

Другой полезный фабричный метод — это correctOf() , который позволяет исключить элементы, переданные в качестве параметров . Давайте создадим EnumSet со всеми элементами Color , кроме черного и белого:

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

Если мы напечатаем эту коллекцию, мы увидим, что она содержит все остальные элементы:

[RED, YELLOW, GREEN, BLUE]

Наконец, мы можем создать EnumSet , скопировав все элементы из другого EnumSet :

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

Внутри он вызывает метод clone .

Более того, мы также можем скопировать все элементы из любой коллекции , содержащей элементы перечисления . Давайте используем его для копирования всех элементов списка:

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

В этом случае listCopy содержит только красный цвет.

4.2. Другие операции

Остальные операции работают точно так же, как и любая другая реализация Set , и нет никакой разницы в том, как их использовать.

Поэтому мы можем легко создать пустой EnumSet и добавить некоторые элементы:

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

Проверьте, содержит ли коллекция определенный элемент:

set.contains(Color.RED);

Итерация по элементам:

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

Или просто удалите элементы:

set.remove(Color.RED);

Это, конечно, среди всех других операций, которые поддерживает Set .

5. Вывод

В этой статье мы показали основные функции EnumSet , его внутреннюю реализацию и то, как мы можем извлечь выгоду из его использования.

Мы также рассмотрели основные методы, которые он предлагает, и реализовали несколько примеров, чтобы показать, как мы можем их использовать.

Как всегда, полный исходный код примеров доступен на GitHub .