본문 바로가기

JAVA

[JAVA] 열거형(enum)


Enum 이란?

enum이 등장하기 전까지 상수(변하지 않는 수)를 정의할 때 아래와 같은 방법으로 상수를 정의하였습니다.

public static final int APPLE = 1;
public static final int BANANA = 2;
public static final int PEACH = 3;

하지만 위와 같이 상수를 정의해서 코딩하는 경우 다양한 문제가 발생합니다.

이러한 문제를 보완하기 위해 자바는 'enum'을 제공합니다

enum은 열거형(enumerated type)이라 부르며, 열거형은 서로 연관된 상수들의 집합을 의미합니다.

우리는 enum을 이용해 다음과 같은 장점을 가질 수 있습니다.

  1. 코드의 가독성을 좋게 할 수 있습니다.
  2. 인스턴스 생성과 상속을 방지하여 상수값의 타입안정성이 보장됩니다.
  3. 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 알 수 있습니다.
  4. enum class를 사용해 정의한 타입 외의 타입을 가진 데이터 값을 컴파일 시 체크

코드 예제와 함께 Enum의 장점을 살펴보도록 하겠습니다.

public class EnumEx {
	// definitnion method before enum
    public static final String APPLE = "APPLE";
    public static final String BANANA = "BANANA";
    
    public static void main(String[] args){
    	String fruit1;
        fruit1 = EnumEx.APPLE;
        fruit1 = EnumEX.BANANA;
        // APPLE, BANANA이 아닌 다른 상수 값이 할당 될 때
        // 컴파일 시 에러가 발생하지 않는다.
        fruit1 = "tomato";
        Fruit fruit2;
        fruit2 = Fruit.APPLE;
        fruit2 = Fruit.BANANA;
        // 컴파일 시 의도치 않은 상수 값은 체크
        // Enum으로 정의한 상숫값만 할당 가능        
        fruit2 = "tomato";
    }
enum Fruit {
	APPLE,
    BANANA;
}    
}

위의 코드 예제를 보면 fruit1 ="tomato"처럼 우리가 기대치 않았던 상수가 할당되었을 때, 컴파일 시 확인하기 어렵습니다. 하지만 코드 예제와 같이 enum 클래스인 fruit2 인스턴스를 사용한다면 enum에 정의하지 않은 상수를 할당하였을 때 컴파일 오류가 발생하게 됩니다.

 

Enum 사용하기

가장 단순한 형태

public enum Seasons {
	SPRING,
    SUMMER,
    FALL,
    WINTER
}

public class EnumEX{
	public String name;
    public Seasons seasons;
}

위의 예제 코드는 별도의 .java 파일선언 방법입니다.

public class enumEx {
	pulbic String name;
    public enum Seasons{
    	SPRING, SUMMER, FALL, WINTER
    }
}

위의 예제 코드는 class 내부에서 선언하는 방법입니다.

enum Season {SPRING, SUMMER, FALL, WINTER}

public class EnumEx {
	public static void main(String[] args) {
    	System.out.println(Season.SPRING);
        
        for (Season s : Season.values()) {
        	System.out.println(s);
        }
    }
}

//출력 결과
SPRING
SPRING
SUMMER
FALL
WINTER

위 코드 예제와 같이 for와 .value()를 사용할 수 있습니다.

 

Switch와 함께 사용하기

switch와 함께 사용 한다면 모든 타입을 나열해야 할 수 있고, 마지막의 throw와 같은 불필요한 코드가 있어야 합니다.

public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    public double apply(double x, double y) {
        switch (this) {
            case PLUS:
                return x + y;
            case MINUS:
                return x - y;
            case TIMES:
                return x * y;
            case DIVIDE:
                return x / y;
        }
        //throw 코드가 있어야함
        throw new AssertionError("Unknown op: " + this);
    }
}

public class Main {
	public static void main(String[] args) {
    	Operation op = Operation.PLUS;
        System.out.println(op.apply(3,4));
    }
}

// 출력 결과

7.0

switch의 대안으로 상수별로 다르게 동작하는 코드 구현

이펙티브 자바 3/E에서는 위와 같은 switch의 대안으로 다음과 같은 방식을 소개합니다

public enum Operation {
    PLUS {
        public  double apply(double x, double y) {return x+y;}},
    MINUS {
        public  double apply(double x, double y) {return x-y;}},
    TIMES {
        public  double apply(double x, double y) {return x*y;}},
    DIVIDE {
        public  double apply(double x, double y) {return x/y;}};
    public abstract double apply(double x, double y);
    
}

public class Main {
    public static void main(String[] args) {
        Operation op = Operation.PLUS;
        System.out.println(op.apply(3,4));

    }
}

// 출력 결과

7.0

@Override toString을 통해 보기 좋게 만들기

toString을 Override하여 위의 코드를 다음과 같이 출력하기 좋게 변경할 수 있습니다.

 

enum Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        public double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        public double apply(double x, double y) { return x / y; }
    };
    
    private final String symbol;
    
    Operation(String symbol) {
    	this.symbol = symbol;
    }
    
    @Override
    public String toString() {
    	return symbol;
	}
    
    public abstract double apply(double x, double y);
}

public class Main {
    public static void main(String[] args) {
        Operation op = Operation.PLUS;
        System.out.println(op.apply(3,4));

    }
}

// 출력 결과

7.0

Interface를 통해 확장하기

enum이 interface를 구현하게 하는 방법은 아래 코드예제와 같습니다.

public interface Operation {
    double apply(double x, double y);
}

public enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x+y; }
    },
    MINUS("-") {
        public double apply(double x, double y) { return x-y; }
    },
    TIMES("*") {
        public double apply(double x, double y) { return x*y; }
    },
    DIVIDE("/") {
        public double apply(double x, double y) { return x+y; }
    };
    
    private final String symbol;
    
    BasicOperation(String symbol) { this.symbol = symbol; }

    @Override
    public String toString() { return this.symbol; }
}

 

value값에 해당하는 enum 객체 가져오기

enum을 사용하다 보면, 특정 값과 일치하는 enum을 찾는 메서드가 필요한 경우가 종종 생깁니다.

다음과 같이 stream을 통해 구현이 가능합니다.

enum Operation {
  	PLUS("+"),
  	MINUS("-"),
  	TIMES("*"),
  	DIVIDE("/");

  	private String operation;

  	Operation(String operation) { this.operation = operation; }

  	static Operation findBy(String operation) {
	    return Arrays.stream(values())
      		.filter(v -> operation.equals(v.operation))
      		.findFirst()
      		.orElseThrow(() -> new IllegalArgumentException(operation + ", 지원하지 않는 연산자입니다!"));
  }
}

간헐적으로 사용하는 케이스인 경우, 위와 같은 방식으로 해도 무방하지만, 열거형 타입이 많고 

빈번하게 호출된다면, 미약한 성능 개선이 필요합니다.

enum Operation {
	PLUS("+"),
	MINUS("-"),
	TIMES("*"),
	DIVIDE("/");

	private String operation;

	Operation(String operation) { this.operation = operation; }
    
	private static final Map<String, Operation> valueToEnumMap = new HashMap<>();
	static {
		for (Operation oper : values()) {
			valueToEnumMap.put(oper.value(), oper);
		}
	}

  	public Operation findBy(String operation) {
	    return valueToEnumMap.get(operation);
	}

	public String value() {
		return operation;
	}
}

 

Clean code : 코드의 가독성 향상

일반적으로 자주 사용하는 함수 인자 패턴입니다.

public class EnumExample {
	public enum RouteSection { ADMIN, SELLER, CUSTOMS };
    
    public String route(RouteSection section, String json) {
    	switch (section) {
        case CUSTOMS:
        	doCustoms(json);
            break;
        case CUSTOMS:
        	doCustoms(json);
            break;
        case CUSTOMS:
        	doCustoms(json);
            break;
        default:
        	doRouteException(json);
        	break;
        }
    }
    
    public void request() {
        route(ADMIN, "{ type: admin }");
    	route(CUSTOMS, "{ type: buyer }");
        route(SELLER, "{ type: seller }");
    }
}

 

안티 패턴: ordinal 메서드의 사용

Java API 문서에서는 enum의 ordinal 메서드에 대해 다음과 같이 말합니다.

Most programmers will have no use for this method.
It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap.
대부분의 프로그래머는 이 메서드를 쓸 일이 없다.
이 메서드는 EnumSet과 EnumMap 같이 열거 타입 기반의 범용 자료구조에 쓸 목적으로 설계되었다.

ordinal 메서드는 단지 해당 상수가 몇 번째인지를 리턴할 뿐이고, 쓸모가 없습니다.

이 값에 의존하는 코드를 작성하는 것도 좋은 선택이 아닙니다.

고로 쓰지 않는 것이 좋습니다.

 


이번 글을 통하여 enum에 대하여 공부하였습니다. 간단한 예제들이지만 이번 글을 통하여 enum의 장점을

느낄 수 있었으면 좋겠습니다.

감사합니다.

 


출처

Java enum의 사용 - 기계 인간

enum 이란? - Lim-Ky

Enum 클래스

열거형(enum) - Opentutorials

Enum 소화하기 - 배부른코딩로그