Java에서 열거 형 상수 만들기

"열거 가능한 상수"집합은 숫자처럼 계산할 수있는 정렬 된 상수 모음입니다. 이 속성을 사용하면 숫자처럼 배열을 인덱싱하거나 for 루프에서 인덱스 변수로 사용할 수 있습니다. Java에서는 이러한 객체를 "열거 형 상수"라고합니다.

열거 형 상수를 사용하면 코드를 더 읽기 쉽게 만들 수 있습니다. 예를 들어, 가능한 값으로 상수 RED, GREEN 및 BLUE를 사용하여 Color라는 새 데이터 유형을 정의 할 수 있습니다. 아이디어는 Car 객체와 같이 사용자가 만든 다른 객체의 속성으로 Color를 갖는 것입니다.

class Car {색상 색상; ...}

그런 다음 다음과 같이 명확하고 읽기 쉬운 코드를 작성할 수 있습니다.

 myCar.color = 빨간색; 

다음과 같은 대신 :

 myCar.color = 3; 

Pascal과 같은 언어에서 열거 형 상수의 더 중요한 속성은 형식이 안전하다는 것입니다. 즉, 색상 속성에 잘못된 색상을 할당 할 수 없습니다. 항상 빨간색, 녹색 또는 파란색이어야합니다. 반대로 색상 변수가 int이면 해당 숫자가 유효한 색상을 나타내지 않더라도 유효한 정수를 할당 할 수 있습니다.

이 문서에서는 다음과 같은 열거 형 상수를 만들기위한 템플릿을 제공합니다.

  • 안전한 유형
  • 인쇄 가능
  • 색인으로 사용하기 위해 주문 됨
  • 연결됨, 앞으로 또는 뒤로 반복
  • 열거 가능

이후 기사에서는 열거 형 상수를 확장하여 상태 종속 동작을 구현하는 방법을 배웁니다.

정적 결승전을 사용하지 않는 이유는 무엇입니까?

열거 형 상수에 대한 일반적인 메커니즘은 다음과 같이 static final int 변수를 사용합니다.

static final int RED = 0; static final int GREEN = 1; static final int BLUE = 2; ...

정적 결승이 유용합니다

최종 값이기 때문에 값은 일정하고 변경할 수 없습니다. 정적이므로 모든 객체에 대해 한 번이 아니라 정의 된 클래스 또는 인터페이스에 대해 한 번만 생성됩니다. 그리고 그것들은 정수 변수이기 때문에 열거되어 인덱스로 사용될 수 있습니다.

예를 들어 루프를 작성하여 고객이 좋아하는 색상 목록을 만들 수 있습니다.

for (int i = 0; ...) {if (customerLikesColor (i)) {favoriteColors.add (i); }}

변수를 사용하여 색상과 관련된 값을 가져 오는 배열 또는 벡터로 인덱싱 할 수도 있습니다. 예를 들어, 플레이어마다 다른 색깔의 조각이있는 보드 게임이 있다고 가정 해 보겠습니다. 각 색상 조각에 대한 비트 맵과 display()해당 비트 맵을 현재 위치에 복사 하는 메서드가 있다고 가정 해 보겠습니다 . 보드에 조각을 올리는 한 가지 방법은 다음과 같습니다.

PiecePicture redPiece = 새로운 PiecePicture (RED); PiecePicture greenPiece = 새 PiecePicture (GREEN); PiecePicture bluePiece = 새 PiecePicture (BLUE);

void placePiece (int location, int color) {setPosition (location); if (color == RED) {display (redPiece); } else if (color == GREEN) {display (greenPiece); } else {display (bluePiece); }}

그러나 정수 값을 사용하여 조각 배열로 인덱싱하면 코드를 다음과 같이 단순화 할 수 있습니다.

PiecePicture [] piece = {new PiecePicture (RED), new PiecePicture (GREEN), new PiecePicture (BLUE)}; void placePiece (int location, int color) {setPosition (location); 디스플레이 (조각 [색상]); }

일정한 범위의 상수와 인덱스를 배열 또는 벡터로 반복 할 수 있다는 것은 정적 최종 정수의 주요 이점입니다. 그리고 선택의 수가 늘어 나면 단순화 효과는 더욱 커집니다.

하지만 정적 인 결승전은 위험합니다

그래도 정적 최종 정수를 사용하는 데는 몇 가지 단점이 있습니다. 가장 큰 단점은 형식 안전성이 없다는 것입니다. 계산되거나 읽혀지는 모든 정수는 그렇게하는 것이 타당한 지 여부에 관계없이 "색상"으로 사용할 수 있습니다. 정의 된 상수의 끝을 지나서 바로 반복하거나 모든 상수를 포함하지 않고 중지 할 수 있습니다. 목록에서 상수를 추가하거나 제거했지만 반복 색인을 조정하는 것을 잊은 경우 쉽게 발생할 수 있습니다.

예를 들어, 색상 선호 루프는 다음과 같이 읽을 수 있습니다.

for (int i = 0; i <= BLUE; i ++) {if (customerLikesColor (i)) {favoriteColors.add (i); }}

나중에 새 색상을 추가 할 수 있습니다.

static final int RED = 0; static final int GREEN = 1; static final int BLUE = 2; static final int MAGENTA = 3;

또는 다음 중 하나를 제거 할 수 있습니다.

static final int RED = 0; static final int BLUE = 1;

두 경우 모두 프로그램이 올바르게 작동하지 않습니다. 색상을 제거하면 문제에주의를 기울이는 런타임 오류가 발생합니다. 색상을 추가하면 오류가 전혀 발생하지 않습니다. 프로그램이 모든 색상 선택을 처리하지 못할뿐입니다.

또 다른 단점은 읽을 수있는 식별자가 없다는 것입니다. 현재 색상 선택을 표시하기 위해 메시지 상자 또는 콘솔 출력을 사용하면 숫자가 표시됩니다. 이는 디버깅을 매우 어렵게 만듭니다.

읽을 수있는 식별자를 만드는 문제는 다음과 같이 정적 최종 문자열 상수를 사용하여 해결되는 경우가 있습니다.

static final String RED = "red".intern (); ...

intern()메서드를 사용하면 내부 문자열 풀에 해당 내용이 포함 된 문자열이 하나만 보장됩니다. 그러나 intern()효과적이려면 RED와 비교되는 모든 문자열 또는 문자열 변수가이를 사용해야합니다. 그럼에도 불구하고 정적 최종 문자열은 루프 또는 배열로의 인덱싱을 허용하지 않으며 여전히 유형 안전성 문제를 해결하지 않습니다.

형식 안전성

정적 최종 정수의 문제점은이를 사용하는 변수가 본질적으로 제한되지 않는다는 것입니다. 그것들은 int 변수입니다. 즉, 보유하고자하는 상수뿐만 아니라 모든 정수를 보유 할 수 있습니다. 목표는 유효하지 않은 값이 해당 변수에 할당 될 때마다 런타임 오류가 아닌 컴파일 오류가 발생하도록 Color 유형의 변수를 정의하는 것입니다.

JavaWorld의 Philip Bishop의 기사 "C ++ 및 Java의 Typesafe 상수"에서 우아한 솔루션이 제공되었습니다.

아이디어는 정말 간단합니다 (보면!).

public final class Color {// 최종 클래스 !! private Color () {} // 개인 생성자 !!

public static final Color RED = new Color (); public static final Color GREEN = new Color (); 공개 정적 최종 색상 BLUE = new Color (); }

클래스가 final로 정의 되었기 때문에 서브 클래 싱 할 수 없습니다. 다른 클래스는 생성되지 않습니다. 생성자가 비공개이기 때문에 다른 메서드는 클래스를 사용하여 새 개체를 만들 수 없습니다. 이 클래스로 생성 될 유일한 객체는 클래스가 처음 참조 될 때 클래스가 자체적으로 생성하는 정적 객체입니다! 이 구현은 클래스를 미리 정의 된 인스턴스 수로 제한하는 Singleton 패턴의 변형입니다. 이 패턴을 사용하여 Singleton이 필요할 때마다 정확히 하나의 클래스를 만들거나 여기에 표시된대로 사용하여 고정 된 수의 인스턴스를 만들 수 있습니다. (Singleton 패턴은 Design Patterns : Elements of Reusable Object-Oriented Software 책에 정의되어 있습니다. Gamma, Helm, Johnson 및 Vlissides, Addison-Wesley, 1995.이 책에 대한 링크는 참고 자료 섹션을 참조하십시오.)

이 클래스 정의의 놀라운 부분은 클래스가 자신 을 사용하여 새 객체를 생성 한다는 것 입니다. RED를 처음 참조하면 존재하지 않습니다. 그러나 RED가 정의 된 클래스에 액세스하는 행위는 다른 상수와 함께 생성되도록합니다. 물론 이러한 종류의 재귀 참조는 시각화하기가 다소 어렵습니다. 그러나 장점은 전체 유형 안전성입니다. Color 유형의 변수에는 Color클래스가 생성 하는 RED, GREEN 또는 BLUE 객체 외에는 할당 할 수 없습니다 .

식별자

typesafe 열거 형 상수 클래스의 첫 번째 개선 사항은 상수의 문자열 표현을 만드는 것입니다. 다음과 같은 행을 사용하여 읽을 수있는 버전의 값을 생성 할 수 있기를 원합니다.

 System.out.println (myColor); 

객체를와 같은 문자 출력 스트림으로 출력 System.out할 때마다, 객체를 문자열에 연결할 때마다 Java는 자동으로 toString()해당 객체에 대한 메소드를 호출 합니다. 이것이 toString()여러분이 생성하는 새로운 클래스에 대한 메서드 를 정의하는 좋은 이유 입니다.

클래스에 toString()메서드 가 없으면 상속 계층 구조를 찾을 때까지 검사합니다. 계층 구조의 맨 위에 toString()있는 Object클래스 의 메서드 는 클래스 이름을 반환합니다. 따라서 toString()방법에는 항상 어떤 의미가 있지만 대부분의 경우 기본 방법은 그다지 유용하지 않습니다.

다음은 Color유용한 toString()메서드 를 제공하는 클래스 수정입니다 .

public final class Color { private String id; private Color ( String anID ) { this.id = anID; } public String toString () {return this.id; }

public static final Color RED = new Color (

"빨간"

); public static final Color GREEN = new Color (

"초록"

); public static final Color BLUE = new Color (

"푸른"

); }

이 버전은 개인용 문자열 변수 (id)를 추가합니다. 생성자는 String 인수를 가져와 개체의 ID로 저장하도록 수정되었습니다. toString()방법은 다음 개체의 ID를 반환합니다.

toString()메서드 를 호출하는 데 사용할 수있는 한 가지 트릭 은 객체가 문자열에 연결될 때 자동으로 호출된다는 사실을 활용합니다. 즉, 다음과 같은 줄을 사용하여 개체 이름을 null 문자열에 연결하여 대화 상자에 넣을 수 있습니다.

 textField1.setText ( ""+ myColor); 

Lisp의 모든 괄호가 마음에 들지 않는 한, 대안보다 조금 더 읽기 쉽습니다.

 textField1.setText (myColor.toString ()); 

닫는 괄호의 올바른 수를 입력하는 것도 더 쉽습니다!

주문 및 인덱싱

다음 질문은 멤버를 사용하여 벡터 또는 배열로 인덱싱하는 방법입니다.

Color

수업. 메커니즘은 각 클래스 상수에 서수를 할당하고 속성을 사용하여 참조하는 것입니다.

.ord

, 다음과 같이 :

void placePiece (int location, int color) {setPosition (location); display (piece [color .ord ]); }

에 시침 만 .ord에 대한 참조를 변환 할 color숫자로하는 것이 특히 꽤 아니라, 어느 몹시 돌출되지 않습니다. typesafe 상수에 대한 상당히 합리적인 절충 인 것 같습니다.

서수를 지정하는 방법은 다음과 같습니다.

public final class Color { private String id; public final int ord;private static int upperBound = 0; private Color(String anID) { this.id = anID; this.ord = upperBound++; } public String toString() {return this.id; } public static int size() { return upperBound; }

public static final Color RED = new Color("Red"); public static final Color GREEN = new Color("Green"); public static final Color BLUE = new Color("Blue"); }

This code uses the new JDK version 1.1 definition of a "blank final" variable -- a variable that is assigned a value once and once only. This mechanism allows each object to have its own non-static final variable, ord, which will be assigned once during object creation and which will thereafter remain immutable. The static variable upperBound keeps track of the next unused index in the collection. That value becomes the ord attribute when the object is created, after which the upper bound is incremented.

For compatibility with the Vector class, the method size() is defined to return the number of constants that have been defined in this class (which is the same as the upper bound).

A purist might decide that the variable ord should be private, and the method named ord() should return it -- if not, a method named getOrd(). I lean toward accessing the attribute directly, though, for two reasons. The first is that the concept of an ordinal is unequivocally that of an int. There is little likelihood, if any, that the implementation would ever change. The second reason is that what you really want is the ability to use the object as though it were an int, as you could in a language like Pascal. For example, you might want to use the attribute color to index an array. But you cannot use a Java object to do that directly. What you would really like to say is:

 display(piece[color]); // desirable, but does not work 

But you can't do that. The minimum change necessary to get what you want is to access an attribute, instead, like this:

 display(piece[color.ord]); // closest to desirable 

instead of the lengthy alternative:

 display(piece[color.ord()]); // extra parentheses 

or the even lengthier:

 display(piece[color.getOrd()]); // extra parentheses and text 

The Eiffel language uses the same syntax for accessing attributes and invoking methods. That would be the ideal. Given the necessity of choosing one or the other, however, I've gone with accessing ord as an attribute. With any luck, the identifier ord will become so familiar as a result of repetition that using it will seem as natural as writing int. (As natural as that may be.)

Looping

다음 단계는 클래스 상수를 반복 할 수있는 것입니다. 처음부터 끝까지 반복 할 수 있기를 원합니다.

 for (색상 c = Color.first (); c! = null; c = c.next ()) {...} 

또는 끝에서 처음으로 :

 for (색상 c = Color.last (); c! = null; c = c.prev ()) {...} 

이러한 수정은 정적 변수를 사용하여 생성 된 마지막 개체를 추적하고 다음 개체에 연결합니다.