본문 바로가기
JAVA

Enum(열거형)

qbang 2021. 12. 21.

개념

열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.

class Card {
    static final int CLOVER = 0;
    static final int HEART = 1;
    static final int DIAMONT = 2;
    static final int SPADE = 3;
    
    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;
    
    final int kind;
    final int num;
}

위 상수들을 enum으로 나타내면,

class Card {
    enum Kind {CLOVER, HEART, DIAMONT, SPADE} //열거형 Kind를 정의
    enum Value {TWO, THREE, FOUR} //열거형 Value를 정의
    
    final Kind kind;
    final Value value;
}

로 나타낼 수 있다. C언어는 타입이 달라도 값이 같으면 조건식결과가 참이였으나, 자바의 열거형은 타입에 안전한 열거형이기 때문에 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다.

if (Card.Kind.CLOVER == Card.Value.TWO)

위 조건식도 값은 0으로 같지만, 타입이 다르기 때문에 컴파일 에러가 발생한다. 

이외에도 일반 상수를 사용하면 해당 상수를 참조하는 모든 소스를 다시 컴파일해야 하지만, 열거형 상수를 사용하면 기존의 소스를 다시 컴파일 하지 않아도 된다.

 

사용

정의

enum 열거형이름 {상수명1, 상수명2, ...}
enum Direction {EAST, SOUTH, WEST, NORTH}

열거형을 정의하는 방법은 괄호 안에 상수의 이름을 나열하는 것이다. 정의된 상수를 사용하는 방법은 클래스의 static 변수를 참조하는 것과 동일하게 '열거형이름.상수명'이다. 예로 동서남북 4방향을 상수로 정의하는 열거형 Direction이 있다.

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

해당 이넘을 클래스로 나타내면 위와 같다. static 상수 EAST, SOUTH, WEST, NORTH의 값은 객체의 주소이고, 이 값은 바뀌지 않는 값이므로 ==으로 비교가 가능하다.

 

출력

Direction d1 = Direction.EAST;
Direction d2 = Direction.valueOf("EAST");
Direction d3 = Enum.valueOf(Direction.class, "EAST");

위 코드에서 System.out.println()으로 d1, d2, d3를 출력하면 모두 "EAST"를 출력하게 된다.

for (Direction d : Direction.values()) {
	System.out.printf("%s = %d%n", d.name(), d.ordinal());
}

다음은 enum 클래스의 모든 상수를 출력한다. values()를 이용하여 열거형의 모든 상수를 배열에 담아 반환받는다. ordinal()은 열거형 상수가 정의된 순서를 기준으로 0부터 차례대로 반환한다.

 

비교

public class Main {
    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 == d2); //false
        System.out.println(d1 == d3); //true
        System.out.println(d1.equals(d2)); //false
        System.out.println(d1.equals(d3)); //true
        System.out.println(d1.compareTo(d2)); //-2
        System.out.println(d1.compareTo(d3)); //0
    }
}

열거형 상수간의 비교에는 '=='와 equals()를 사용할 수 있다. 크고 작음을 비교할 때는 '<'나 '>'를 사용할 수 없고, compareTo()를 사용해서 비교할 수 있다. 

d1과 d2는 각각 EAST와 WEST를 나타내므로 첫번째 출력문의 결과는 false이다. 

d1과 d3는 모두 EAST를 나타내므로 두번째 출력문의 결과는 true이다.

d1과 d2는 각각 EAST와 WEST를 나타내므로 세번째 출력문의 결과는 false이다.

d1과 d3는 모두 WEST를 나타내므로 네번째 출력문의 결과는 true이다.

d1은 EAST로 첫번째로 정의되어 있으므로 0 값을 가지고, d2는 WEST로 세번째로 정의되어 있으므로 2 값을 가진다. 따라서 d1이 더 작으므로 음수를 반환한다.

d1과 d3는 동일한 0 값을 가진다. 따라서 compareTo()는 0을 반환한다.

 

멤버 추가

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

enum Direction {
    EAST(1), SOUTH(5), WEST(-1), NORTH(10);
    
    private final int value;
    Direction(int value) {
    	this.value = value;
    }
    // 외부에서 값을 얻을 수 있도록 getter 함수를 추가
    public int getValue() {
    	return value;
    }
}

열거형 상수의 값이 불연속적인 경우에는 이름 옆에 원하는 값을 ()와 함께 적어준다. 그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해주어야 한다. 이때 열거형 상수가 멤버 변수보다 앞에 정의되어있어야 한다.

enum Direction {
    EAST(1, ">"), ...
    
    private final int value;
    private final String symbol;
    
    Direction(int value, String symbol) {
    	this.value = value;
        this.symbol = symbol;
    }
    ...
}

하나가 아니라 여러 개의 값을 지정할 경우 그에 맞게 인스턴스 변수를 추가하고 생성자를 수정해주면 된다.

Direction d = new Direction(1); //error

열거형의 생성자는 묵시적으로 private이기 때문에 위와 같이 호출할 수 없다.

 

추상 메서드 추가

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

거리에 따라 요금을 계산하는 방식이 운송 수단마다 다를 것이다. 이럴 때는 열거형에 추상 메서드 'fare(int distance)'를 선언하면 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 한다. 해당 코드에서는 똑같이 구현했지만, 다르게 구현될 수 있는 가능성이 있는 추상 메서드로 선언하여 구현을 강제화하면 된다. 메서드는 Transportation.BUS.fare(100)과 같이 사용할 수 있다.

 

이외에도 Enum 클래스는 다음과 같은 메서드가 정의되어 있다.

  • Class<E> getDeclaringClass(): 열거형의 Class 객체를 반환한다.
  • String name(): 열거형 상수의 이름을 문자열로 반환한다.
  • int ordinal(): 열거형 상수가 정의된 순서를 반환한다.(0부터 시작)
  • T valueOf(Class<T> enumType, String name): 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
  • E valueOf(String name): 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다. 

 

본 게시글은 책 자바의 정석을 바탕으로 작성되었습니다.

댓글