**1. Введение**
В этом руководстве мы рассмотрим коллекцию EnumSet
из пакета java.util
и обсудим ее особенности.
Сначала мы покажем основные возможности коллекции, а после этого пройдемся по внутренностям класса, чтобы понять его преимущества.
Наконец, мы рассмотрим основные операции, которые он предоставляет, и реализуем несколько основных примеров.
2. Что такое EnumSet
EnumSet — это
специализированная коллекция Set для работы с
перечислимыми
классами . Он реализует интерфейс Set
и расширяется от AbstractSet
:
Несмотря на то, что 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
:
В 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 .