자바

📖 Enum 열거형

newwisdom 2021. 8. 2. 02:42
반응형

2021-02-21

열거형이란?

JDK 1.5 부터 새로 추가된 개념이다.

기존의 언어들과 자바의 Enum이 다른 점은

  • 열거형이 갖는 값 뿐만 아니라 타입까지 관리하기 때문에 보다
    논리적인 오류를 줄일 수 있다.
  • '타입에 안전한 열거형'을 제공하여 실제 값이 같아도 타입이 다르면 조건식 결과가 false이다. 값 뿐만 아니라 타입까지 체크한다.
  • 상수 값이 바뀌어도 기존 소스를 다시 컴파일하지 않아도 된다.

열거형의 정의와 사용

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

열거형에 정의된 상수를 사요아는 방법은 열거형이름.상수명이다.
클래스의 static 변수를 참조하는 것과 동일하다.

열거형 상수간 비교

== 연산자를 사용하여 비교할 수 있다.
equals()가 아닌 ==으로 비교가 가능하다는 것은 그만큼 성능이 좋다는 것이다.
<, >로는 비교할 수 없지만 compareTo()는 사용이 가능하다.

switch 문의 조건식에도 사용할 수 있다.

switch(d1) {
    case EAST: // Direction.EAST라고 쓰면 안된다.
        System.out.println("The direction is EAST."); 
        break;
    case SOUTH:
        System.out.println("The direction is SOUTH."); 
        break;
    case WEST:
        System.out.println("The direction is WEST."); 
        break;
    case NORTH:
        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.original());
  • values() : 열거형의 모든 상수를 배열에 담아 반환한다.
  • ordinal() : 모든 열거형의 조상인 java.lang.Enum 클래스에 정의된 것으로, 정의된 순서를 정수로 반환한다.
  • name() : 열거형 상수의 이름을 문자열로 반환한다.
  • valueOf() : 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.

열거형의 생성자

Enum 타입은 열거형을 의미하는 특별한 형태의 클래스이기 때문에 일반 클래스와 같이
생성자가 존재하여애 한다. 자바가 기본 생성자를 만들어주긴 하지만,
열거형의 생성자는 제어자가 묵시적으로 private으로 지정해줘야 한다.

이유는 고정된 상수의 집합으로 런타임이 아닌 컴파일 타임에 모든 값을
알고 있어야 하기 때문이다.
즉, 다른 패키지나 클래스에서 접근해 동적으로 값을 할당할 수 없다.

열거형에 멤버 추가하기

oridinal()이 열거형 상수가 정의된 순서를 반환하지만,
내부적인 용도로만 사용되기 위한 것이기 때문에 열거형 상수의 값으로 사용하지 않는 것이 좋다.

열거 상수의 값이 불규칙적인 경우에는 다음과 같이 열거형 상수 이름 옆에
원하는 값을 괄호와 함께 적어준다.
그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해주어야 한다.

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

    private final int value; // 정수를 저장할 필드(인스턴스 변수) 추가
    Direction(int value) {this.value = value;}

    public int getValue() {return value;}
}

필요에 따라 하나의 열거형 상수에 여러 값을 지정할 수 있다.
이에 맞게 인스턴스 변수와 생성자 등을 새로 추가해주어야 한다.

enum Direction {
    EAST(1, ">"), SOUTH(5, "V"), WEST(-1, "<"), NORTH(10, "^");

    private final int value; // 정수를 저장할 필드(인스턴스 변수) 추가
    private final String symbol;
    Direction(int value, String symbol) {
        this.value = value;
        this.symbol = symbol;
    }

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

Example

enum Direction { 
    EAST(1, ">"), SOUTH(2,"V"), WEST(3, "<"), NORTH(4,"^");

    private static final Direction[] DIR_ARR = Direction.values();
    private final int value;
    private final String symbol;

    Direction(int value, String symbol) { // private Direction(int value)
        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

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));
    }
}

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

Example

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 getBasicFare() { return BASIC_FARE; }

    abstract int fare(int distance); // 거리에 따른 요금 계산
}

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));
    }
}

Transportation은 운송 수단의 종류 별로 상수를 정의하고 있고,
각 운송 수단에는 기본요금이 책정되어 있다.
여기서 거리에 따라 요금을 계산하는 방식이 각 운송 수단마다 다른 경우를 위해,
추상 메서드 fare(int distance)를 선언해 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 한다.

열거형에 추상 메서드를 선언할 일은 그리 많지 않다.

열거형의 비교

enum Direction { EAST, SOUTH, WEST, NORTH; }

이 열거형 상수 하나하나가 Direction 객체이다.
Direction 클래스의 static 상수 EAST, SOUTH, WEST, NORTH의 값은 객체의 주소이고,
이 값은 바뀌지 않는 값이므로 ==로 비교가 가능하다.

Example

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();
    }
}

객체가 생성될 때마다 번호를 붙여서 인스턴스 변수 ordinal에 저장한다.
그리고 Comparable 인터페이스를 구현해서 열거형 상수간의 비교가 가능하도록 되어 있다.

만일 클래스를 MyEnum<T>와 같이 선언하였다면, compareTo()를 위와 같이
간단히 작성할 수 없었을 것이다.
타입 T에 ordinal()이 정의되어 있는지 확인할 수 없기 때문이다.
그래서 MyEnum<T extends<MyEnum<T>>와 같이 선언한 것이며,
이것은 타입 T가 MyEnum<T>의 자손이어야 한다는 의미이다.
타입 T가 MyEnum의 자손이므로 ordinal()이 정의되어 있는 것은 분명하므로,
형변환 없이도 에러가 나지 않는다.

그리고 추상 메서드를 새로 추가하면, 클래스 앞에도 abstract를 붙여줘야 하고,
각 static 상수들도 추상 메서드를 구현해주어야 한다.

참고 자료

  • 자바의 정석 3판
  • [Java: enum의 뿌리를 찾아서](
반응형