Java에서 typesafe 열거 형을 사용하는 방법

기존 열거 유형을 사용하는 Java 코드는 문제가 있습니다. Java 5는 typesafe enum의 형태로 더 나은 대안을 제공했습니다. 이 기사에서는 열거 형 및 형식 안전 열거 형을 소개하고 형식 안전 열거 형을 선언하고 switch 문에서 사용하는 방법을 보여주고 데이터와 동작을 추가하여 형식 안전 열거 형을 사용자 지정하는 방법에 대해 설명합니다. 수업 을 탐색하면서 기사를 마무리합니다 .java.lang.Enum

다운로드 코드 받기이 Java 101 자습서의 예제에 대한 소스 코드를 다운로드합니다. JavaWorld /를 위해 Jeff Friesen이 만들었습니다.

열거 형에서 형식이 안전한 열거 형으로

열거 된 타입은 그것의 값과 관련된 상수의 집합을 지정한다. 예를 들어 요일, 표준 북 / 남 / 동 / 서 나침반 방향, 통화의 동전 단위 및 어휘 분석기의 토큰 유형이 있습니다.

열거 형은 전통적으로 정수 상수의 시퀀스로 구현되어 왔으며 다음과 같은 방향 상수 세트로 설명됩니다.

static final int DIR_NORTH = 0; static final int DIR_WEST = 1; static final int DIR_EAST = 2; static final int DIR_SOUTH = 3;

이 접근 방식에는 몇 가지 문제가 있습니다.

  • 형식 안전성 부족 : 열거 형식 상수는 정수일 뿐이므로 상수가 필요한 곳에 모든 정수를 지정할 수 있습니다. 또한 이러한 상수에 대해 더하기, 빼기 및 기타 수학 연산을 수행 할 수 있습니다. 예를 들어 (DIR_NORTH + DIR_EAST) / DIR_SOUTH)는 의미가 없습니다.
  • 네임 스페이스가 없음 : 열거 형의 상수는 DIR_다른 열거 형의 상수와의 충돌을 방지하기 위해 일종의 (희망적으로는) 고유 식별자 (예 :)를 접두사로 지정해야합니다 .
  • Brittleness : 열거 형 상수는 리터럴 값이 저장된 (상수 풀에) 클래스 파일로 컴파일되기 때문에 상수 값을 변경하려면 이러한 클래스 파일과 이에 종속 된 해당 응용 프로그램 클래스 파일을 다시 빌드해야합니다. 그렇지 않으면 정의되지 않은 동작이 런타임에 발생합니다.
  • 정보 부족 : 상수가 인쇄되면 정수 값이 출력됩니다. 이 출력은 정수 값이 나타내는 것에 대해 아무것도 알려주지 않습니다. 상수가 속한 열거 형을 식별하지도 않습니다.

java.lang.String상수 를 사용하면 "형식 안전성 부족"및 "정보 부족"문제를 피할 수 있습니다. 예를 들어 static final String DIR_NORTH = "NORTH";. 상수 값이 더 의미가 있지만 String기반 상수는 여전히 "이름 공간이 없음"및 취성 문제로 어려움을 겪습니다. 또한 정수 비교와 달리 문자열 값을 ==and !=연산자 (참조 만 비교) 와 비교할 수 없습니다 .

이러한 문제로 인해 개발자는 Typesafe Enum 이라는 클래스 기반 대안을 발명했습니다 . 이 패턴은 널리 설명되고 비판되었습니다. Joshua Bloch는 자신의 Effective Java Programming Language Guide (Addison-Wesley, 2001)의 항목 21에서 패턴을 도입했으며 몇 가지 문제가 있다고 언급했습니다. 즉, 형식이 안전한 열거 형 상수를 집합으로 집계하는 것이 어색하고 열거 형 상수를 switch문 에서 사용할 수 없습니다 .

typesafe enum 패턴의 다음 예제를 고려하십시오. 이 Suit클래스는 클래스 기반 대안을 사용하여 네 가지 카드 슈트 (클럽, 다이아몬드, 하트 및 스페이드)를 설명하는 열거 유형을 도입하는 방법을 보여줍니다.

public final class Suit // Suit를 하위 클래스로 분류 할 수 없습니다. {public static final Suit CLUBS = new Suit (); public static final Suit DIAMONDS = new Suit (); public static final Suit HEARTS = new Suit (); public static final Suit SPADES = new Suit (); private Suit () {} // 추가 상수를 도입 할 수 없어야합니다. }

이 클래스를 사용하려면 다음과 같이 Suit변수를 도입하고 Suit의 상수 중 하나에 할당 합니다.

슈트 슈트 = Suit.DIAMONDS;

그런 다음 심문 할 수 있습니다 suitA의 switch이 같은 성명 :

switch (suit) {case Suit.CLUBS : System.out.println ( "clubs"); 단절; case Suit.DIAMONDS : System.out.println ( "diamonds"); 단절; case Suit.HEARTS : System.out.println ( "하트"); 단절; case Suit.SPADES : System.out.println ( "spades"); }

그러나 Java 컴파일러가를 발견 Suit.CLUBS하면 상수 표현식이 필요하다는 오류를보고합니다. 다음과 같이 문제를 해결할 수 있습니다.

switch (정장) {case CLUBS : System.out.println ( "clubs"); 단절; 케이스 다이아몬드 : System.out.println ( "diamonds"); 단절; case HEARTS : System.out.println ( "하트"); 단절; case SPADES : System.out.println ( "spades"); }

그러나 컴파일러가를 만나면 CLUBS기호를 찾을 수 없다는 오류를보고합니다. 그리고 당신은 배치해도 Suit컴파일러는 변환 할 수 없습니다 불평 것, 패키지에서 패키지를 수입하고, 정적이 상수를 수입 Suitint발생하는 경우 suitswitch(suit). 각각 case에 대해 컴파일러는 상수 표현식이 필요하다고보고합니다.

Java는 switch문이 있는 Typesafe Enum 패턴을 지원하지 않습니다 . 그러나 문제를 해결하는 동안 패턴의 이점을 캡슐화하기 위해 typesafe enum 언어 기능을 도입 했으며이 기능은 switch.

typesafe 열거 형 선언 및 switch 문에서 사용

Java 코드의 간단한 typesafe 열거 형 선언은 C, C ++ 및 C # 언어의 해당 항목과 유사합니다.

enum 방향 {NORTH, WEST, EAST, SOUTH}

이 선언은 키워드 enum를 사용하여 Direction임의의 메서드를 추가하고 임의의 인터페이스를 구현할 수있는 typesafe enum (특별한 종류의 클래스)으로 도입합니다. 는 NORTH, WEST, EAST, 및 SOUTHENUM 상수 둘러싸고 연장 익명 클래스 정의 상수 특정 클래스 물체로서 구현되는 Direction클래스.

Direction다른 형태 보증 된 열거 형은 확장  등 다양한 방법을, 상속 , 그리고 이 클래스에서. 이 기사의 뒷부분에서 살펴 보겠습니다 .Enum values()toString()compareTo()Enum

목록 1은 앞서 언급 한 열거 형을 선언하고 switch문 에서 사용합니다 . 또한 두 개의 열거 형 상수를 비교하여 다른 상수보다 먼저 오는 상수를 확인하는 방법도 보여줍니다.

목록 1 : TEDemo.java(버전 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .values ​​() [i]; System.out.println (d); switch (d) {case NORTH : System.out.println ( "Move north"); 단절; case WEST : System.out.println ( "Move west"); 단절; case EAST : System.out.println ( "동쪽으로 이동"); 단절; case SOUTH : System.out.println ( "Move south"); 단절; 기본값 : 거짓 주장 : "알 수없는 방향"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

목록 1은 Directiontypesafe 열거 형을 선언하고 values()반환 되는 상수 멤버를 반복 합니다. 각 값에 대해 switch문 (typesafe 열거 형을 지원하도록 향상됨)은의 case값에 해당  하는를 선택하고 d 적절한 메시지를 출력합니다. (예를 들어, 열거 형 상수에 접두사를 붙이지 않습니다 NORTH.) 마지막으로 목록 1 은 앞에 오는지 Direction.NORTH.compareTo(Direction.SOUTH)확인하기 위해 평가 됩니다 .NORTHSOUTH

다음과 같이 소스 코드를 컴파일하십시오.

javac TEDemo.java

다음과 같이 컴파일 된 응용 프로그램을 실행합니다.

자바 TEDemo

다음 출력을 관찰해야합니다.

NORTH 북쪽으로 이동 WEST 서쪽으로 이동 EAST 동쪽으로 이동 SOUTH 남쪽으로 이동 -3

The output reveals that the inherited toString() method returns the name of the enum constant, and that NORTH comes before SOUTH in a comparison of these enum constants.

Adding data and behaviors to a typesafe enum

You can add data (in the form of fields) and behaviors (in the form of methods) to a typesafe enum. For example, suppose you need to introduce an enum for Canadian coins, and that this class must provide the means to return the number of nickels, dimes, quarters, or dollars contained in an arbitrary number of pennies. Listing 2 shows you how to accomplish this task.

Listing 2: TEDemo.java (version 2)

enum Coin { NICKEL(5), // constants must appear first DIME(10), QUARTER(25), DOLLAR(100); // the semicolon is required private final int valueInPennies; Coin(int valueInPennies) { this.valueInPennies = valueInPennies; } int toCoins(int pennies) { return pennies / valueInPennies; } } public class TEDemo { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java TEDemo amountInPennies"); return; } int pennies = Integer.parseInt(args[0]); for (int i = 0; i < Coin.values().length; i++) System.out.println(pennies + " pennies contains " + Coin.values()[i].toCoins(pennies) + " " + Coin.values()[i].toString().toLowerCase() + "s"); } }

Listing 2 first declares a Coin enum. A list of parameterized constants identifies four kinds of coins. The argument passed to each constant represents the number of pennies that the coin represents.

The argument passed to each constant is actually passed to the Coin(int valueInPennies) constructor, which saves the argument in the valuesInPennies instance field. This variable is accessed from within the toCoins() instance method. It divides into the number of pennies passed to toCoin()’s pennies parameter, and this method returns the result, which happens to be the number of coins in the monetary denomination described by the Coin constant.

At this point, you’ve discovered that you can declare instance fields, constructors, and instance methods in a typesafe enum. After all, a typesafe enum is essentially a special kind of Java class.

The TEDemo class’s main() method first verifies that a single command-line argument has been specified. This argument is converted to an integer by calling the java.lang.Integer class’s parseInt() method, which parses the value of its string argument into an integer (or throws an exception when invalid input is detected). I’ll have more to say about Integer and its cousin classes in a future Java 101 article.

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()참조를 통해 상수를 비교하도록 재정의됩니다. 동일한 ID ( ==)를 가진 상수 는 동일한 내용 ( equals())을 가져야 하며 다른 ID는 다른 내용을 의미합니다.
  • finalize() 상수를 마무리 할 수 ​​없도록 재정의됩니다.
  • hashCode()재정의 되었기 때문에 equals()재정의되었습니다.
  • toString() 상수의 이름을 반환하도록 재정의됩니다.

Enum또한 자체 방법을 제공합니다. 이 방법은 포함 finalcompareTo() ( Enum구현하는 java.lang.Comparable인터페이스) getDeclaringClass(), name()ordinal()방법 :