Language/JAVA

[JAVA]열거형 (enums)

1. 열거형이란?

열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것

여러 상수를 정의할 때 사용하면 유용, JDK1.5부터 추가, 논리적 오류 줄이는데 도움

 

Example Code

더보기
Class Card {
	static final int CLOVER = 0;
    static final int HEART = 1;
    static final int DIAMOND = 2;
    static fianl int SPADE = 3;
    
    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;
    
    final int kind;
    final int num;
}

=>

class Card {
	enum Kind { CLOVER, HEART, DIAMOND, SPADE }	// 열거형 Kind를 정의
    	enum Value { TWO, THREE, FOUR }		// 열거형 Value를 정의
    
    final Kind kind;	// 타입이 int가 아닌 Kind임에 유의
    final Value value;
}

자바의 열거형은 '타입에 안전한 열거형(typesafe enum)'이라서 값 뿐만 아니라 타입까지 체크

if(Card.CLOVER == Card.TWO)	// true지만 false이어야 의미상 맞음.
if(Card.kIND.CLOVER == Card.value.TWO)	// 컴파일 에러. 값은 같지만 타입이 다름

상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 컴파일 해야하지만

열거형 상수를 사용하면, 기존의 소스를 다시 컴파일 하지 않아도 된다.

2. 열거형의 정의와 사용

열거형을 정의하는 방법

enum 열거형이름 { 상수명1, 상수명2, ... }

열거형을 사용하는 방법

 

Example Code

더보기
enum Direction { EAST, SOUTH, WEST, NORTH }

class Unit {
	int x, y;	// 유닛의 위치
    Direction dir;	// 열거형을 인스턴스 변수로 선언
    
    void init() {
		dir = Direction.EAST;	// 유닛의 방향을 EAST로 초기화
	}
}

열거형 상수간 비교에는 '=='를 사용할 수 있어 equals()보다 빠른 성능의 비교가 가능

그러나 '<', '>'와 같은 비교연산자는 사용이 불가하고 compareTo()는 사용가능

switch문의 조건식에도 열거형 사용가능 ( 열거형의 이름은 적지 않고 상수의 이름만 적어야 함)

 

모든 열거형의 조상 - java.lang.Enum

Enum.values(); : 해당 열거형 class의 모든 상수를 리턴

Enum.ValueOf(); : 열거형 상수의이름으로 상수에 대한 참조

메서드 설명
Class<E> getDeclaringClass() 열거형의 Class객체를 반환한다.
String name() 열거형 상수의 이름을 문자열로 반환
int ordinal() 열거형 상수가 정의된 순서를 반환 (0부터 시작)
T valueOf(Class<T> enumType, String name) 지정된 열거형에서 name과 일치하는 열거형 상수를 반환

 

 

Example Code

더보기

EnumEx1.java ( Enum 생성 및 사용 예제 코드)

package kr.co.dong.enumex;

enum Direction { EAST, SOUTH, WEST, NORTH }

public class EnumEx1 {
	public static void main(String[] args) {
		Direction d1 = Direction.EAST;
		Direction d2 = Direction.valueOf("WEST");
		Direction d3 = Enum.valueOf(Direction.class, "EAST");
		
		System.out.println("d1="+d1);
		System.out.println("d2="+d2);
		System.out.println("d3="+d3);
		
		System.out.println("d1==d2 ? " + (d1==d2));
		System.out.println("d1==d3 ? " + (d1==d3));
		System.out.println("d1.equals(d3) ? " + d1.equals(d3));
//		System.out.println("d2 > d3 ? " + (d1 > d3));	//에러
		System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3)));
		System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2)));
		
		switch (d1) {
		case EAST:	// Direction.EAST
			System.out.println("The direction is EAST."); break;
		case WEST:	// Direction.EAST
			System.out.println("The direction is WEST."); break;
		case NORTH:	// Direction.EAST
			System.out.println("The direction is NORTH."); break;
		default:
			System.out.println("Invalid direction."); break;
		}
		
		Direction[] dArr = Direction.values();
		
		for(Direction d : dArr) {
			System.out.printf("%s=%d\n",d.name(), d.ordinal());
		}
	}
}

/*
실행결과

d1=EAST
d2=WEST
d3=EAST
d1==d2 ? false
d1==d3 ? true
d1.equals(d3) ? true
d1.compareTo(d3) ? 0
d1.compareTo(d2) ? -2
The direction is EAST.
EAST=0
SOUTH=1
WEST=2
NORTH=3
*/

 

3. 열거형에 멤버 추가하기

Enum클래스에 정의된 ordinal()는 열거형 상수가 정의된 순서를 반환하나, 이 값을 열거형 상수의 값으로 사용하지 않는 것이 좋다.

열거형 상수의 값을 불연속적이게 대입 가능하기 때문이다.

enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }

Example Code

더보기
enum Direction {
	EAST(1), SOUTH(5), WEST(-1), NORTH(10);	// 끝에 ';'를 추가해야 한다.
    
    private final int value;	// 정수를 저장할 필드(인스턴스 변수)를 추가
    // 열거형 상수의 값을 저장하기 위한 것이므로 final 키워드 사용
    Direction(int value)	{ this.value = value; }	// 생성자를 추가
    
    public int getValue()	{ return value; }
}
Direction d = new Direction(1);  // 에러. 열거형의 생성자는 외부에서 호출불가
enum Direction {
	...
    Direction(int value) {}  // private Direction(int value)와 동일
    // 열거형의 생성자는 제어자가 묵시적으로 private이기 때문.
}

열거형 상수에 여러 값 지정가능, 단 그에 맞게 인스턴스 변수와 생성자 등을 새로 추가

enum Direction {
	EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");
    
    private final int value;
    private final String symbol;
    
    Direction(int value, String symbol) {	// 접근 제어자 private이 생략됨
    this.value = value;
    this.symbol = symbol;
    }
    
    public int getValue()	{ return value; }
    public String getSymbol()	{ return symbol; }
}

EnumEx2.java ( 여러값을 지정한 ENum을 활용하는 예제 코드)

package kr.co.dong.enumex;

enum Direction {
	EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");	
	
    private static final Direction[] DIR_ARR = Direction.values();
    private final int value;
    private String symbol;
    
	private Direction(int value, String symbol) {	// 접근 제어자 private 생략
		this.value = value;
		this.symbol = symbol;
	}

	public int getValue() { return value; }
	public String getSymbol() { return symbol; }

	public static Direction of(int dir) {
		if (dir < 1 || dir > 4) {
			throw new IllegalArgumentException("Invalid value : " + dir);
		}
		return DIR_ARR[dir-1];
	}
	
	// 방향을 회전시키는 메서드. num의 값만큼 90도씩 시계방향으로 회전한다.
	public Direction rotate(int num) {
		num = num % 4;
		if(num < 0) num += 4;	// num이 음수일 때는 시계반대 방향으로 회전
		return DIR_ARR[(value-1+num) % 4];
	}
	
	public String toString() {
		return name() + getSymbol();
	}
}	// enum Direction

public class EnumEx2 {
	public static void main(String[] args) {
		for(Direction d : Direction.values()) {
			System.out.printf("%s=%d\n", d.name(), d.getValue());
		}
		
		Direction d1 = Direction.EAST;
		Direction d2 = Direction.of(1);
		
		System.out.printf("d1=%s, %d\n",d1.name(), d1.getValue());
		System.out.printf("d2=%s, %d\n",d2.name(), d2.getValue());
		
		System.out.println(Direction.EAST.rotate(1));
		System.out.println(Direction.EAST.rotate(2));
		System.out.println(Direction.EAST.rotate(-1));
		System.out.println(Direction.EAST.rotate(-2));
	}
}
/*
실행결과
EAST=1
SOUTH=2
WEST=3
NORTH=4
d1=EAST, 1
d2=EAST, 1
SOUTHV
WEST<
NORTH^
WEST<
*/

열거형에 추상 메서드 추가하기

 

Example Code

더보기

열거형에 정의된 추상메서드를 각 상수가 어떻게 구현하는지 보여주는 예제 코드

 

enum Transportation {
	BUS(100) { int fare(int distance) { return distance*BASIC_FARE; }},
	TRAIN(150) { int fare(int distance) { return distance*BASIC_FARE; }},
	SHIP(100) { int fare(int distance) { return distance*BASIC_FARE; }},
    AIRPLANE(300) { int fare(int distance) { return distance*BASIC_FARE; }};
    
    abstract int fare(int distance);	// 거리에 따른 요금을 계산하는 추상 메서드
    
    protected final int BASIC_FARE;	// protected로 해야 각 상수에서 접근가능
    
    Transportation(int basicFare) {
    	BASIC_FARE = basicFare;
    }
	
    public int getBasicFare() { return BASIC_FARE; }
}
package kr.co.dong.enumex;

enum Transportation {
	BUS(100) { int fare(int distance) { return distance*BASIC_FARE; }},
	TRAIN(150) { int fare(int distance) { return distance*BASIC_FARE; }},
	SHIP(100) { int fare(int distance) { return distance*BASIC_FARE; }},
    AIRPLANE(300) { int fare(int distance) { return distance*BASIC_FARE; }};
    
	protected final int BASIC_FARE;	// protected로 해야 각 상수에서 접근가능
    
    Transportation(int basicFare) {	// private Transportation(int basicFare)
    	BASIC_FARE = basicFare;
    }
	
    public int getBaiscFare() { return BASIC_FARE; }
    abstract int fare(int distance);	// 거리에 따른 요금을 계산하는 추상 메서드
}

public class EnumEx3 {
	public static void main(String[] args) {
		System.out.println("bus fare = " +Transportation.BUS.fare(100));
		System.out.println("train fare = " +Transportation.TRAIN.fare(100));
		System.out.println("ship fare = " +Transportation.SHIP.fare(100));
		System.out.println("airplane fare = " +Transportation.AIRPLANE.fare(100));
	}
}
/*
실행결과
bus fare = 10000
train fare = 15000
ship fare = 10000
airplane fare = 30000
*/

4. 열거형의 이해

열거형 상수 하나하나가 Direction객체이다.

 

Example Code

더보기

열거형 상수 하나하나가 Direction객체이다는 것을 보여주는 예제 코드

enum Direction { EAST, SOUTH, WEST, NORTH }

class Direction {
	static final Direction EAST = new Direction("EAST");
    static final Direction SOUTH = new Direction("SOUTH");
    static final Direction WEST = new Direction("WEST");
    static final Direction NORTH = new Direction("NORTH");
    
    private String name;
    
    private Direction(String name) {
    	this.name = name;
    }
}

추상 클래스 Enum을 흉내낸 예제 코드

abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
	static int id = 0;	// 객체에 붙일 일련번호 (0부터 시작)
    
    int ordinal;
    String name = "";
    
    public int ordinal() { return ordinal; }
    
    MyEnum(String name) {
    	this.name = name;
        ordinal = id++;	// 객체를 생성할 때마다 id의 값을 증가시킨다.
    }
    
    public int compareTo(T t) {
    	return ordinal - t.ordinal();
    }
}

 generics와 enum의 ortinal을 활용한 비교

abstract class MyEnum<T> implements Comparable<T> {
	...
    public int compareTo(T t) {
    	return ordinal - t.ordinal(); //에러. 타입 T에 ordinal()이 있나?
    }
}

 

익명 클래스 형태로 추상 메서드 구현

abstract class Direction extends MyEnum {
	static final Direction EAST = new Direction("EAST") {	// 익명 클래스
    	Point move(Point p) { /* 이하생략 */ }
	};
    static final Direction SOUTH = new Direction("SOUTH") {	// 익명 클래스
    	Point move(Point p) { /* 이하생략 */ }
	};
    static final Direction WEST = new Direction("WEST") {	// 익명 클래스
    	Point move(Point p) { /* 이하생략 */ }
	};
    static final Direction NORTH = new Direction("NORTH") {	// 익명 클래스
    	Point move(Point p) { /* 이하생략 */ }
	};
    
    private String name;
    
    private Direction(String name) {
    	this.name = name;
    }
    abstract Point move(Point p);
}

EnumEx4.java (Enum 마지막 예제)

package kr.co.dong.enumex;

abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
	static int id = 0;
	int ordinal;
	String name = "";
	
	public int ordinal() { return ordinal; }
	
	MyEnum(String name){
		this.name = name;
		ordinal = id++;
	}
	
	public int compareTo(T t) {
		return ordinal - t.ordinal();
	}
}

abstract class MyTransportation extends MyEnum {
	static final MyTransportation BUS = new MyTransportation("BUS", 100) {
		int fare(int distance) { return distance * BASIC_FARE; }
	};
	static final MyTransportation TRAIN = new MyTransportation("TRAIN", 150) {
		int fare(int distance) { return distance * BASIC_FARE; }
	};
	static final MyTransportation SHIP = new MyTransportation("SHIP", 100) {
		int fare(int distance) { return distance * BASIC_FARE; }
	};
	static final MyTransportation AIRPLANE = new MyTransportation("AIRPLANE", 300) {
		int fare(int distance) { return distance * BASIC_FARE; }
	};
	
	abstract int fare(int distance);	// 추상 메서드
	
	protected final int BASIC_FARE;
	
	private MyTransportation(String name, int basicFare) {
		super(name);
		BASIC_FARE = basicFare;
	}
	
	public String name() { return name; }
	public String toString() { return name; }
}
public class EnumEx4 {
	public static void main(String[] args) {
		MyTransportation t1 = MyTransportation.BUS;
		MyTransportation t2 = MyTransportation.BUS;
		MyTransportation t3 = MyTransportation.TRAIN;
		MyTransportation t4 = MyTransportation.SHIP;
		MyTransportation t5 = MyTransportation.AIRPLANE;
		
		System.out.printf("t1=%s, %d\n", t1.name(), t1.ordinal());
		System.out.printf("t2=%s, %d\n", t2.name(), t2.ordinal());
		System.out.printf("t3=%s, %d\n", t3.name(), t3.ordinal());
		System.out.printf("t4=%s, %d\n", t4.name(), t4.ordinal());
		System.out.printf("t5=%s, %d\n", t5.name(), t5.ordinal());
		System.out.println("t1==t2 ? " + (t1==t2));
		System.out.println("t1.compareTo(t3) = " + t1.compareTo(t3));
	}
}
/*
실행결과
t1=BUS, 0
t2=BUS, 0
t3=TRAIN, 1
t4=SHIP, 2
t5=AIRPLANE, 3
t1==t2 ? true
t1.compareTo(t3) = -1
*/