Ръководство за EnumSet

1. Въведение

В този урок ще изследваме колекцията EnumSet от пакета java.util и ще обсъдим нейните особености.

Първо ще покажем основните характеристики на колекцията и след това ще преминем през вътрешността на класа, за да разберем предимствата му.

И накрая, ще разгледаме основните операции, които предоставя, и ще приложим някои основни примери.

2. Какво представлява EnumSet

Един EnumSet е специализирана Set колекция за работа с ENUM класове . Той реализира интерфейса Set и се простира от AbstractSet :

Въпреки че AbstractSet и AbstractCollection предоставят реализации за почти всички методи на интерфейсите Set и Collection , EnumSet заменя повечето от тях.

Когато планираме да използваме EnumSet , трябва да вземем предвид някои важни моменти:

  • Той може да съдържа само стойности на преброяване и всички стойности трябва да принадлежат към един и същ преброяване
  • Не позволява да се добавят нулеви стойности , като се прави NullPointerException в опит да се направи това
  • Не е безопасно за нишки , така че трябва да го синхронизираме външно, ако е необходимо
  • Елементите се съхраняват в реда, в който са декларирани в изброяването
  • Той използва безопасен итератор, който работи върху копие, така че няма да хвърли ConcurrentModificationException, ако колекцията е модифицирана при итерация върху нея

3. Защо да използваме EnumSet

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

В следващите раздели ще видим какво прави тази колекция по-добра от другите. За да го направим, ще покажем накратко вътрешните части на класа, за да получим по-добро разбиране.

3.1. Подробности за изпълнението

EnumSet е публичен абстрактен клас, който съдържа множество статични фабрични методи, които ни позволяват да създаваме екземпляри. JDK предоставя 2 различни реализации - са частни за пакета и подкрепени от битов вектор:

  • RegularEnumSet и
  • JumboEnumSet

RegularEnumSet използва единичен long за представяне на битовия вектор. Всеки бит от дългия елемент представлява стойност на изброяването . I-тата стойност на изброяването ще се съхранява в i-тия бит, така че е доста лесно да се разбере дали дадена стойност присъства или не. Тъй като long е 64-битов тип данни, тази реализация може да съхранява до 64 елемента.

От друга страна, JumboEnumSet използва масив от дълги елементи като битов вектор. Това позволява на тази реализация да съхранява повече от 64 елемента. Той работи почти като RegularEnumSet, но прави някои допълнителни изчисления, за да намери индекса на масива, където се съхранява стойността.

Не е изненадващо, че първият дълъг елемент от масива ще съхранява 64-те първи стойности на преброяването , вторият елемент - следващите 64 и т.н.

Фабричните методи на EnumSet създават екземпляри на една или друга реализация в зависимост от броя на елементите на enum :

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

Имайте предвид, че той отчита само размера на класа enum , а не броя на елементите, които ще се съхраняват в колекцията.

3.2. Ползи от използването на EnumSet

Поради изпълнението на EnumSet , което описахме по-горе, всички методи в EnumSet се изпълняват с помощта на аритметични битови операции. Тези изчисления са много бързи и следователно всички основни операции се изпълняват за постоянно време.

Ако сравним EnumSet с други реализации на Set като HashSet , първият обикновено е по-бърз, защото стойностите се съхраняват в предвидим ред и за всеки изчисление трябва да се изследва само един бит. За разлика от HashSet , няма нужда да се изчислява хеш-код, за да се намери правилната група.

Освен това, поради естеството на битовите вектори, EnumSet е много компактен и ефективен. Следователно, той използва по-малко памет, с всички предимства, които носи.

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

По-голямата част от методите на EnumSet работят като всеки друг набор , с изключение на методите за създаване на екземпляри.

В следващите раздели ще покажем подробно всички методи за създаване и ще разгледаме накратко останалите методи.

В нашите примери ще работим с Color enum :

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

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

Най-лесните методи за създаване на EnumSet са allOf () и noneOf () . По този начин можем лесно да създадем EnumSet, съдържащ всички елементи на нашия Color enum:

EnumSet.allOf(Color.class);

По същия начин можем да използваме noneOf (), за да направим обратното и да създадем празна колекция от Color :

EnumSet.noneOf(Color.class);

Ако искаме да създадем EnumSet с подмножество от елементите на enum , можем да използваме методите overloaded of () . Важно е да се прави разлика между методите с фиксиран брой параметри до 5 различни и този, който използва varargs :

Javadoc заявява, че производителността на версията на varargs може да бъде по-бавна от останалите поради създаването на масива. Следователно трябва да го използваме само ако първоначално трябва да добавим повече от 5 елемента.

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

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

В горния пример EnumSet съдържа всички елементи от жълто до синьо. Те следват реда, определен в преброяването :

[YELLOW, GREEN, BLUE]

Забележете, че включва както първия, така и последния посочен елемент.

Друг полезен фабричен метод е complementOf (), който ни позволява да изключим елементите, предадени като параметри . Нека създадем EnumSet с всички цветни елементи с изключение на черно и бяло:

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

Ако отпечатаме тази колекция, можем да видим, че тя съдържа всички останали елементи:

[RED, YELLOW, GREEN, BLUE]

И накрая, можем да създадем EnumSet, като копираме всички елементи от друг EnumSet :

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

Вътрешно той извиква метода на клониране .

Освен това можем също да копираме всички елементи от всяка колекция, която съдържа елементи с изброяване . Нека го използваме за копиране на всички елементи от списък:

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

В този случай listCopy съдържа само червения цвят.

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

Останалите операции работят по абсолютно същия начин като всяка друга реализация на Set и няма разлика в това как да ги използвате.

Следователно можем лесно да създадем празен EnumSet и да добавим някои елементи:

EnumSet 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.