Прикачване на стойности към Java Enum

1. Въведение

Типът на изброяването на Java предоставя поддържан от езика начин за създаване и използване на константи. Чрез дефиниране на краен набор от стойности, enum е по-безопасен за типа от постоянните променливи в литерала като String или int .

Стойностите на изброяването обаче се изискват, за да бъдат валидни идентификатори и се насърчаваме да използваме SCREAMING_SNAKE_CASE по конвенция.

Като се има предвид тези ограничения, за ENUM стойност сам не е подходящ за хора, четящи се или не-низови стойности .

В този урок ще използваме функциите на enum като Java клас, за да прикачим стойностите, които искаме.

2. Използване на Java Enum като клас

Често създаваме enum като прост списък със стойности. Например, тук са първите два реда на периодичната таблица като просто изброяване :

public enum Element { H, HE, LI, BE, B, C, N, O, F, NE }

Използвайки горния синтаксис, създадохме десет статични, последни екземпляра на преброяването с име Element . Въпреки че това е много ефективно, ние сме заснели само символите на елементите. И докато формата с главни букви е подходяща за константи на Java, не е как обикновено пишем символите.

Освен това липсват и други свойства на елементите на периодичната таблица, като името и атомното тегло.

Въпреки че типът enum има специално поведение в Java, ние можем да добавяме конструктори, полета и методи, както правим с други класове. Поради това можем да подобрим нашия списък, за да включим ценностите, от които се нуждаем.

3. Добавяне на конструктор и крайно поле

Нека започнем с добавяне на имената на елементите. Ще зададем имената в крайна променлива с помощта на конструктор :

public enum Element { H("Hydrogen"), HE("Helium"), // ... NE("Neon"); public final String label; private Element(String label) { this.label = label; } }

На първо място, забелязваме специалния синтаксис в списъка с декларации. Ето как се извиква конструктор за типове enum . Въпреки че е незаконно използването на новия оператор за изброяване , можем да предадем аргументи на конструктора в списъка на декларациите.

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

Първо, избрахме идентификатора на етикета вместо името . Въпреки че името на полето на член е достъпно за използване, нека изберете етикет, за да избегнем объркване с предварително дефинирания метод Enum.name () .

На второ място, полето ни с етикет е окончателно . Въпреки че полетата на изброяване не трябва да са окончателни , в повечето случаи не искаме етикетите ни да се променят. В духа на постоянните стойности на изброяването това има смисъл.

И накрая, полето на етикета е публично. Следователно можем да получим директен достъп до етикета:

System.out.println(BE.label);

От друга страна, полето може да бъде частно , достъпно с метод getLabel () . За краткост тази статия ще продължи да използва стила на публичното поле.

4. Намиране на стойности на Java Enum

Java предоставя метод valueOf (String) за всички типове enum . По този начин винаги можем да получим enum стойност въз основа на декларираното име:

assertSame(Element.LI, Element.valueOf("LI"));

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

public static Element valueOfLabel(String label) { for (Element e : values()) { if (e.label.equals(label)) { return e; } } return null; }

Методът static valueOfLabel () повтаря стойностите на Element , докато намери съвпадение. Връща нула, ако не е намерено съвпадение. И обратно, изключение може да бъде хвърлено, вместо да връща null .

Нека да видим бърз пример, използвайки метода valueOfLabel () :

assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. Кеширане на справочните стойности

Можем да избегнем повторение на стойностите на изброяването , като използваме Map за кеширане на етикетите . За целта дефинираме статична окончателна карта и я попълваме, когато класът се зареди:

public enum Element { // ... enum values private static final Map BY_LABEL = new HashMap(); static { for (Element e: values()) { BY_LABEL.put(e.label, e); } } // ... fields, constructor, methods public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } }

В резултат на това, че са складирани на ENUM стойности се повтори само веднъж , а valueOfLabel () метод се опростят.

Като алтернатива можем да изградим мързеливо кеша, когато за първи път е достъпен в метода valueOfLabel () . В този случай достъпът до картата трябва да бъде синхронизиран, за да се предотвратят проблеми с едновременността.

6. Прикачване на множество стойности

Конструкторът Enum може да приема множество стойности . За илюстрация нека добавим атомния номер като int и атомното тегло като плувка :

public enum Element { H("Hydrogen", 1, 1.008f), HE("Helium", 2, 4.0026f), // ... NE("Neon", 10, 20.180f); private static final Map BY_LABEL = new HashMap(); private static final Map BY_ATOMIC_NUMBER = new HashMap(); private static final Map BY_ATOMIC_WEIGHT = new HashMap(); static { for (Element e : values()) { BY_LABEL.put(e.label, e); BY_ATOMIC_NUMBER.put(e.atomicNumber, e); BY_ATOMIC_WEIGHT.put(e.atomicWeight, e); } } public final String label; public final int atomicNumber; public final float atomicWeight; private Element(String label, int atomicNumber, float atomicWeight) { this.label = label; this.atomicNumber = atomicNumber; this.atomicWeight = atomicWeight; } public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } public static Element valueOfAtomicNumber(int number) { return BY_ATOMIC_NUMBER.get(number); } public static Element valueOfAtomicWeight(float weight) { return BY_ATOMIC_WEIGHT.get(weight); } }

По подобен начин можем да добавим всякакви стойности, които искаме, към преброяването , като подходящите символи за регистър, например „Той“, „Li“ и „Бъди“.

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

7. Управление на интерфейса

В резултат на добавянето на полета и методи към нашия списък сме променили неговия публичен интерфейс. Следователно нашият код, който използва основните методи Enum name () и valueOf () , няма да знае за новите ни полета.

Методът static valueOf () вече е дефиниран за нас от езика Java. Следователно не можем да предоставим собствена реализация на valueOf () .

По същия начин, тъй като методът Enum.name () е окончателен, ние също не можем да го заменим .

В резултат на това няма практически начин да използваме нашите допълнителни полета, използвайки стандартния API на Enum . Вместо това, нека разгледаме някои различни начини да изложим нашите полета.

7.1. Замяна на toString ()

Замяна на toString () може да бъде алтернатива на заместващото име () :

@Override public String toString() { return this.label; }

По подразбиране Enum.toString () връща същата стойност като Enum.name ().

7.2. Внедряване на интерфейс

Типът enum в Java може да реализира интерфейси . Въпреки че този подход не е толкова общ като Enum API, интерфейсите ни помагат да обобщим.

Нека разгледаме този интерфейс:

public interface Labeled { String label(); }

За съгласуваност с метода Enum.name () , нашият метод label () няма префикс get .

И тъй като методът valueOfLabel () е статичен , ние не го включваме в нашия интерфейс.

И накрая, можем да приложим интерфейса в нашия enum :

public enum Element implements Labeled { // ... @Override public String label() { return label; } // ... }

Едно предимство на този подход е, че етикетираният интерфейс може да се приложи към всеки клас, не само към типове enum . Вместо да разчитаме на общия Enum API, сега имаме по-специфичен за контекста API.

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

В тази статия разгледахме много функции на изпълнението на Java Enum . Чрез добавяне на конструктори, полета и методи виждаме, че enum може да направи много повече от буквални константи.

Както винаги, пълният изходен код за тази статия може да бъде намерен в GitHub.