«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 и JumboEnumSet

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: множество. Поэтому мы должны использовать его только в том случае, если нам изначально нужно добавить более 5 элементов.

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

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

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

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

[YELLOW, GREEN, BLUE]

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

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

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

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

[RED, YELLOW, GREEN, BLUE]

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

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

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

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

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

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.

set.remove(Color.RED);

5. Заключение

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

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

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

«