Как да използвам typesafe enum в Java

Java кодът, който използва традиционни изброени типове, е проблематичен. Java 5 ни даде по-добра алтернатива под формата на безопасни преброявания. В тази статия ви запознавам с изброените типове и typesafe изброявания, показвам ви как да декларирате typesafe преброяване и да го използвате в оператор за превключване и обсъждаме персонализирането на typesafe преброяване чрез добавяне на данни и поведения. Приключвам статията, като изследвам класа.java.lang.Enum

изтегляне Вземете кода Изтеглете изходния код за примери в този урок за Java 101. Създадено от Jeff Friesen за JavaWorld /.

От изброени типове до безопасни преброявания

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

Изброените типове традиционно се прилагат като последователности от целочислени константи, което се демонстрира от следния набор от константи на посоката:

статичен финал int DIR_NORTH = 0; статичен финал int DIR_WEST = 1; статичен финал int DIR_EAST = 2; статичен финал int DIR_SOUTH = 3;

Има няколко проблема с този подход:

  • Липса на безопасност на типа: Тъй като изброената константа на типа е просто цяло число, всяко цяло число може да бъде посочено там, където се изисква константата. Освен това на тези константи могат да се извършват събиране, изваждане и други математически операции; например, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), което е безсмислено.
  • Пространството от имена не присъства: Константите на изброения тип трябва да имат префикс с някакъв (надявам се) уникален идентификатор (например DIR_), за да се предотвратят сблъсъци с константи на друг изброен тип.
  • Крепкост: Тъй като изброените константи от тип се компилират в файлове на класа, където се съхраняват техните литерални стойности (в постоянни пулове), промяната на стойността на константа изисква тези файлове на класове и тези файлове от класове на приложения, които зависят от тях, да бъдат възстановени. В противен случай по време на изпълнение ще възникне недефинирано поведение.
  • Липса на информация: Когато се отпечатва константа, се извежда нейната цялостна стойност. Този изход не ви казва нищо за това какво представлява целочислената стойност. Той дори не идентифицира изброения тип, към който принадлежи константата.

Можете да избегнете проблемите с „липсата на безопасност на типа“ и „липсата на информация“, като използвате java.lang.Stringконстанти. Например можете да посочите static final String DIR_NORTH = "NORTH";. Въпреки че константната стойност е по-значима, Stringбазирани константи все още страдат от „пространство на имена не присъства“ и проблеми с чупливостта. Също така, за разлика от цели числа сравнения, не можете да сравнявате низови стойности със ==и !=оператори (които сравняват само препратки).

Тези проблеми накараха разработчиците да измислят базирана на класа алтернатива, известна като Typesafe Enum . Този модел е широко описан и критикуван. Джошуа Блох представи модела в т. 21 от своето Ефективно ръководство за езици за програмиране на Java (Addison-Wesley, 2001) и отбеляза, че той има някои проблеми; а именно, че е неудобно да се агрегират константи на typesafe enum в множества и че константите на изброяване не могат да се използват в switchизрази.

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

публичен окончателен клас Suit // Не трябва да може да подклас Suit. {публичен статичен финален костюм CLUBS = нов костюм (); публичен статичен окончателен костюм ДИАМАНТИ = нов костюм (); публичен статичен окончателен костюм СЪРЦА = нов костюм (); публичен статичен окончателен костюм SPADES = нов костюм (); private Suit () {} // Не трябва да може да въвежда допълнителни константи. }

За да използвате този клас, трябва да въведете Suitпроменлива и да я присвоите на една от Suitконстантите на, както следва:

Костюм костюм = Suit.DIAMONDS;

След това може да искате да разпитате suitв switchизявление като това:

превключвател (костюм) {случай Suit.CLUBS: System.out.println ("клубове"); почивка; дело Suit.DIAMONDS: System.out.println ("диаманти"); почивка; дело Suit.HEARTS: System.out.println ("сърца"); почивка; дело Suit.SPADES: System.out.println ("пика"); }

Когато обаче се срещне компилаторът на Java Suit.CLUBS, той съобщава за грешка, в която се посочва, че е необходим постоянен израз. Можете да опитате да разрешите проблема по следния начин:

превключвател (костюм) {случай КЛУБОВЕ: System.out.println ("клубове"); почивка; дело ДИАМАНТИ: System.out.println ("диаманти"); почивка; дело СЪРЦА: System.out.println ("сърца"); почивка; калъф SPADES: System.out.println ("пика"); }

Когато обаче срещне компилаторът CLUBS, той ще докладва за грешка, заявявайки, че не е успял да намери символа. И дори ако сте поставили Suitв пакет, импортирали сте пакета и сте импортирали статично тези константи, компилаторът ще се оплаче, че не може да конвертира Suitв, intкогато срещне suitв switch(suit). По отношение на всеки case, компилаторът също ще докладва, че е необходим постоянен израз.

Java не поддържа модела Typesafe Enum с switchизрази. Въпреки това, той въведе функцията за език на преброяване typesafe, за да капсулира предимствата на шаблона, докато решава проблемите му, и тази функция поддържа switch.

Деклариране на typesafe enum и използването му в switch оператор

Една проста декларация за безопасно типово изброяване в Java код изглежда като аналозите му в езиците C, C ++ и C #:

посока на изброяването {СЕВЕР, ЗАПАД, ИЗТОК, ЮГ}

Тази декларация използва ключовата дума enumза въвеждане Directionкато безопасно преброяване (специален вид клас), в който могат да се добавят произволни методи и да се внедрят произволни интерфейси. Най- NORTH, WEST, EAST, и SOUTHконстанти ENUM са реализирани като органи постоянни специфични за класа, които определят анонимни класове, простиращи обхващащата Directionклас.

Directionи други typesafe enums простират  и наследяват различни методи, включително , и , от този клас. Ще разгледаме по-късно в тази статия.Enum values()toString()compareTo()Enum

Листинг 1 декларира гореспоменатото изброяване и го използва в switchизявление. Той също така показва как да сравняваме две константи на преброяване, за да определим коя константа идва преди другата константа.

Листинг 1: TEDemo.java(версия 1)

публичен клас TEDemo {enum Посока {СЕВЕР, ЗАПАД, ИЗТОК, ЮГ} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .values ​​() [i]; System.out.println (d); превключвател (d) {случай СЕВЕР: System.out.println ("Придвижване на север"); почивка; дело ЗАПАД: System.out.println ("Придвижване на запад"); почивка; дело ИЗТОК: System.out.println ("Придвижване на изток"); почивка; дело ЮГ: System.out.println ("Придвижване на юг"); почивка; по подразбиране: твърди false: "неизвестна посока"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Листинг 1 декларира Directiontypesafe enum и итерира над неговите постоянни членове, което се values()връща. За всяка стойност switchизразът (подобрен, за да поддържа безопасни преброявания на типове) избира този, caseкойто съответства на стойността  d и извежда подходящо съобщение. (Не поставяте префикс на enum константа, напр. NORTH, С нейния тип enum.) И накрая, Листинг 1 оценява, за Direction.NORTH.compareTo(Direction.SOUTH)да определи дали NORTHидва преди SOUTH.

Компилирайте изходния код, както следва:

javac TEDemo.java

Стартирайте компилираното приложение, както следва:

java TEDemo

Трябва да наблюдавате следния изход:

СЕВЕР Придвижване на север ЗАПАД Придвижване на запад ИЗТОК Придвижване на изток ЮГ Преместване на юг -3

Резултатът разкрива, че наследеният toString()метод връща името на константата на преброяването и това NORTHидва преди SOUTHв сравнение на тези константи на преброяване

Добавяне на данни и поведения към безопасно преброяване на типове

Можете да добавяте данни (под формата на полета) и поведения (под формата на методи) към безопасно преброяване. Да предположим например, че трябва да въведете преброяване за канадски монети и че този клас трябва да предоставя средства за връщане на броя никели, стотинки, четвъртинки или долари, съдържащи се в произволен брой стотинки. Листинг 2 ви показва как да изпълните тази задача.

Листинг 2: TEDemo.java(версия 2)

enum Coin {NICKEL (5), // константите трябва да се появят първо DIME (10), QUARTER (25), DOLLAR (100); // точката с запетая се изисква private final int valueInPennies; Монета (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} публичен клас TEDemo {публична статична невалидна основна (String [] args) {if (args.length! = 1) {System.err.println ("употреба: java TEDemo amountInPennies"); връщане; } int pennies = Integer.parseInt (аргументи [0]); for (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (стотинки + "стотинки съдържа" + Coin.values ​​() [i] .toCoins (стотинки) + "" + Coin .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Листинг 2 първо декларира Coinизброяване. Списък с параметризирани константи идентифицира четири вида монети. Аргументът, предаден на всяка константа, представлява броят стотинки, които представлява монетата.

Аргументът, предаден на всяка константа, всъщност се предава на Coin(int valueInPennies)конструктора, който записва аргумента в valuesInPenniesполето на екземпляра. Тази променлива е достъпна от toCoins()метода на потребителския модел. Той се разделя на броя на пари преминават да toCoin()е penniesпараметър, и този метод връща резултата, който се случва да бъде броят на монети в паричната наименование описан от Coinконстанта.

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

Методът на TEDemoкласа main()първо проверява дали е зададен единичен аргумент от командния ред. Този аргумент се преобразува в цяло число чрез извикване на метода на java.lang.Integerкласа parseInt(), който анализира стойността на неговия низ аргумент в цяло число (или хвърля изключение, когато е открит невалиден вход). Ще имам повече да кажа за Integerи неговите братовчедни класове в една бъдеща статия за Java 101 .

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()е заменено за сравняване на константи чрез техните препратки. Константите с еднакви идентичности ( ==) трябва да имат едно и също съдържание ( equals()), а различните идентичности предполагат различно съдържание.
  • finalize() е заменено, за да се гарантира, че константите не могат да бъдат финализирани.
  • hashCode()е заменено, защото equals()е отменено.
  • toString() е заменено, за да върне името на константата.

Enumсъщо предоставя свои собствени методи. Тези методи включват finalcompareTo() ( Enumизпълнява java.lang.Comparableинтерфейс), getDeclaringClass(), name(), и ordinal()методи: