[Java] Enum
Java열거형 Enum
C언어의 열거형보다 더 향상되어 논리적인 오류를 줄일 수 있다 → Typesafe enum. 타입에 안전하다.
- C언어 : 타입이 달라도 값이 같으면 조건식의 결과가 true
- Java : 실제 값이 같아도 타입이 다르면 조건식의 결과가 false
열거형 상수를 사용하면, 상수 값이 바뀔 때 해당 상수를 참조하는 모든 소스를 다시 컴파일하지 않아도 된다.
public class Test {
static final int ZERO = 0;
static final int ONE = 1;
static final int TWO = 2;
static final int THREE = 3;
static final int FOUR = 4;
}
public class Test {
enum Number {ZERO, ONE, TWO, THREE, FOUR}
}
정의
괄호{} 안에 상수의 이름을 나열한다.
enum enumName {name1, name2, name3, ...}
사용
열거형이름.상수명
public class Test {
enum Number {ZERO, ONE, TWO, THREE, FOUR}
Number num; // 열거형을 인스턴스 변수로 선언
Test() {
num = Number.ONE; // ONE으로 초기화
}
}
- 열거형 상수간의 비교
equals()가 아닌 '=='로 비교 가능하다.
비교 연산자(>, < 등)는 사용할 수 없고, compareTo()를 사용 할 수 있다.
switch문의 조건식에도 열거형을 사용할 수 있다.
if (num == Number.ZERO) {
} else if (num > Number.TWO) { // error
} else if (num.compareTo(Number.TWO) > 0) {
}
switch (num) {
case ZERO: break;
case ONE: break;
case TWO: break;
case THREE: break;
case FOUR: break;
}
case문에 열거형의 이름은 적지 않고 상수의 이름만 적어서 사용한다.
java.lang.Enum
모든 열거형의 조상
열거형에 정의된 모든 상수를 출력하려면 다음과 같이 한다.
public class Test {
enum Number {
ZERO, ONE, TWO, THREE, FOUR
}
public static void main(String[] args) {
Number[] nArr = Number.values();
for (Number n : nArr) {
System.out.println(n.name() + " " + n.ordinal());
}
}
}
ZERO 0
ONE 1
TWO 2
THREE 3
FOUR 4
- 모든 열거형에 컴파일러가 자동으로 추가해 주는 메서드
values() | 열거형의 모든 상수를 배열에 담아 반환한다. |
valuesOf() | 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다. |
- Public methods
반환 타입 | 이름 | 설명 |
---|---|---|
final int | compareTo(E o) | 두 비교 대상이 같으면 0, 왼쪽이 크면 양수, 오른쪽이 크면 음수를 반환 |
final boolean | equals(Object other) | 지정된 객체가 열거형 상수와 같으면 true |
final Class | getDeclaringClass() | 열거형의 Class 객체를 반환 |
final int | hashCode() | 열거형 상수의 해시 코드를 반환 |
final String | name() | 열거형 상수의 이름을 문자열로 반환 |
final int | ordinal() | 열거형 상수가 정의된 순서(0~)를 정수로 반환 |
String | toString() | 열거형 상수의 이름을 문자열로 반환 |
static <T extends Enum> T | valueOf(Class enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수를 반환 |
- Example
public class Test {
enum Number {
ZERO, ONE, TWO, THREE, FOUR
}
public static void main(String[] args) {
Number n1 = Number.ZERO;
Number n2 = Number.valueOf("TWO");
Number n3 = Enum.valueOf(Number.class, "FOUR");
System.out.println(n1);
System.out.println(n2);
System.out.println(n3);
System.out.println(n1 == n2);
System.out.println(n1 == n3);
System.out.println(n1.equals(n3));
System.out.println(n1.compareTo(n2));
System.out.println(n1.compareTo(n3));
switch (n1) {
case ZERO: System.out.println("n1 = 0"); break;
case ONE: System.out.println("n1 = 1"); break;
case TWO: System.out.println("n1 = 2"); break;
case THREE: System.out.println("n1 = 3"); break;
case FOUR: System.out.println("n1 = 4"); break;
}
for (Number n : Number.values()) {
System.out.println(n.name() + " " + n.ordinal());
}
}
}
ZERO
TWO
FOUR
false
false
false
-2
-4
n1 = 0
ZERO 0
ONE 1
TWO 2
THREE 3
FOUR 4
멤버 추가하기
- 열거형 상수의 값이 불규칙적인 경우
1. 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적는다.
2. 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가한다.
주의! 열거형 상수를 모두 정의한 후 다른 멤버들을 추가한다.
열거형 상수의 마지막에 ' ; '을 잊지 않도록 주의한다.
열거형의 생성자는 제어자가 묵시적으로 private이다.
enum Number {
ZERO(10), ONE(-1), TWO(2), THREE(33), FOUR(-4);
private final int value;
Number(int value) { // == private Number(int value)
this.value = value;
}
public int getValue() {
return value;
}
}
- 하나의 열거형 상수에 여러 값을 지정할 경우
enum Number {
ZERO(10, "Z"), ONE(-1, "O"), TWO(2, "T"), THREE(33, "H"), FOUR(-4, "F");
private final int value;
private final String symbol;
Number(int value, String symbol) {
this.value = value;
this.symbol = symbol;
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
추상 메서드 추가하기
열거형에 추상 메서드를 선언하면 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 한다.
enum Number {
ZERO(10, "Z") {
@Override
String getNumber() { return NUMBER + 0; }
}, ONE(-1, "O") {
@Override
String getNumber() { return NUMBER + 1; }
}, TWO(2, "T") {
@Override
String getNumber() { return NUMBER + 2; }
}, THREE(33, "H") {
@Override
String getNumber() { return NUMBER + 3; }
}, FOUR(-4, "F") {
@Override
String getNumber() { return NUMBER + 4; }
};
abstract String getNumber();
protected final String NUMBER = "Number = "; // protected로 해야 각 상수에서 접근 가능.
...
}
이해하기
열거형 Number가 다음과 같이 정의되어 있을 때, 열거형 상수 하나하나가 Number 객체다.
enum Number {ZERO, ONE, TWO, THREE, FOUR}
클래스로 정의한다면 다음과 같다.
public class Number {
static final Number ZERO = new Number("ZERO");
static final Number ONE = new Number("ONE");
static final Number TWO = new Number("TWO");
static final Number THREE = new Number("THREE");
static final Number FOUR = new Number("FOUR");
private String name;
private Number(String name) {
this.name = name;
}
}
Number 클래스의 static 상수들의 값은 객체의 주소이다.
이 값은 바뀌지 않으므로 '=='로 비교 가능하다.
모든 열거형의 조상인 추상 클래스 Enum을 흉내 내어 작성하면 다음과 같다.
abstract class Test<T extends Test<T>> implements Comparable<T> {
static int id = 0; // 객체에 붙일 일련번호. 0~
int ordinal;
String name = "";
public int ordinal() {
return ordinal;
}
Test(String name) {
this.name = name;
ordinal = id++; // 객체를 생성할 때마다 id의 값을 증가시킨다.
}
public int compareTo(T t) {
return ordinal - t.ordinal();
}
}
객체가 생성될 때마다 번호를 붙여서 인스턴스 변수 ordinal에 저장한다.
Comparable 인터페이스를 두 열거형 상수의 ordinal 값을 서로 빼도록 구현해서 열거형 상수 간의 비교가 가능하도록 되어 있다.
클래스를 Test<T>로 선언했다면 타입 T에 ordinal()이 정의되어 있는 지 확인할 수 없어 compareTo()를 위와 같이 작성할 수 없었을 것이다.
Test<T extends Test<T>>는 타입 T가 Test<T>의 자손이어야 한다는 의미이므로
Test의 자손인 타입 T는 ordinal()이 정의되어 있는 것이 분명하기 때문에 형변환 없이도 에러가 나지 않는다.
참고 서적: 자바의 정석 3판 2