자바 Enum 기본 및 활용

카일·2020년 2월 23일
22

자바 Enum

목록 보기
1/2
post-thumbnail

정의

Enum이란 Enumeration의 앞 글자로 열거라는 의미를 갖는다. 관련이 있는 상수들의 집합입니다. 자바에서는 final로 String과 같은 문자열이나 숫자들을 나타내는 기본 자료형의 값을 고정할 수 있습니다. 이렇게 고정된 값을 상수라고 합니다. 영어로는 constant입니다. 어떤 클래스가 상수만으로 작성되어 있으면 반드시 class로 선언할 필요는 없습니다. 이럴 때 class로 선언된 부분에 enum이라고 선언하면 이 객체는 상수의 집합이다. 라는 것을 명시적으로 나타냅니다.

기존에는 인터페이스나 클래스 내에서 상수를 선언함으로써 상수를 관리 하였는데 클래스 내에서 선언하는 부분은 네이밍이 겹칠 수 있고 불 필요하게 상수가 많아지는 단점이 있다.
인터페이스로 관리하는 경우 이런 부분은 줄어들지만 여전히 IDE의 지원을 적극적으로 받을 수 없고 타입 안정성이 떨어지는 단점을 가지고 있었다. 이를 보완하며 나온 것이 Enum이다.

특징

1. 클래스를 상수처럼 사용 할 수 있다.

  • 아래의 코드는 Enum Class의 예제이다. 생성자가 존재하지만 Default 생성자는 private 로 되어 있으며 public 으로 변경하는 경우 컴파일 에러가 발생한다. 즉 다른 클래스나 인터페이스에서의 상수선언이 클래스 로드 시점에서 생성되는 것 처럼 Enum 또한 생성자가 존재하지만 클래스가 로드되는 시점에서 생성되기 때문에 임의로 생성하여 사용 할 수 없다. 이를 사용하고자 한다면 Rank.FIVE 와 같은 형태로 상수처럼 사용하면 된다.
    public enum  Rank {
    	THREE(3, 4_000),
    	FOUR(4, 10_000),
    	FIVE(5, 30_000);
    
    	private final int match;
    	private final int money;
    	private int count;
    
    	Rank(int match, int money) {  // Default 생성자는 private 으로 설정되어 있음.
    		this.match = match;
    		this.money = money;
    	}

2. Enum 클래스를 구현하는 경우 상수 값과 같이 유일하게 하나의 인스턴스가 생성되어 사용된다.

  • Enum 클래스에서 선언한 상수들은 클래스가 로드될 때 하나의 인스턴스로 생성되어 싱글톤 형태로 어플리케이션 전체에서 사용된다. 싱글톤 으로 사용되기 때문에 각각의 Enum 인스턴스에 변수를 추가하여 사용하는 것은 Multi Thread 환경에서 위험할 수 있다. 아래의 예시를 보면 각각의 인스턴스에 count 라는 변수가 추가되어 있는데 외부에서 각 등수에 맞게 plusCount() 를 호출 할 수 있다. 하지만 멀티 쓰레드 환경에서는 각 인스턴스의 count가 공유되고 있기 때문에 조심해야 한다.
    public enum Rank {
    	THREE(3, 4_000),
    	FOUR(4, 10_000),
    	FIVE(5, 30_000);
    	
    	private final int match;
    	private final int money;
    	private int count;
    	
    	Rank(int match, int money) {
    		this.match = match;
    		this.money = money;
    	}
    
    	public void plusCount() {
    		this.count++;
    	}
    }

3. 서로 관련 있는 상수 값들을 모아 enum으로 구현하는 경우 유용하다.

4. 클래스와 같은 문법 체계를 따른다.

5. 상속을 지원하지 않는다.

  • 모든 enum들은 내부적으로 java.lang.enum 클래스에 의해 상속된다. 자바는 다중 상속을 지원하지 않기 때문에 Enum은 다른 클래스를 상속받을 수 없다. 참고로 toString() 메서드는 상수의 이름을 리턴하도록 구현되어 있다.
        public static void main(String[] args) {
        		System.out.println(Rank.FIVE.toString());
        	}
        
        // 결과 : FIVE
  • 기존에 상속받고 있는 클래스가 존재하기 때문에 다중 상속은 지원하지 않지만 다양한 인터페이스 들은 구현할 수 있다.

Enum의 내부 Api

위에서 언급한 java.lang.Enum 클래스를 기본적으로 상속받고 있기 때문에 아래의 세 가지 메소드를 지원한다. (부모 클래스의 메소드라 사용 가능하다.)

1. values()

  • values() 는 Enum 클래스가 가지고 있는 모든 상수 값을 배열의 형태로 리턴 한다. 참고로 단순히 String 의 형태로 단순 반환하는 것이 아니라 인스턴스를 반환하는 것이다. 즉 Enum 클래스가 가지고 있는 모든 인스턴스를 배열에 담아 반환하는 것이다.
    public static void main(String[] args) {
    		Rank[] values = Rank.values();
    		for(int i = 0; i< values.length; i++) {
    			System.out.println(values[i]);
    		}
    }
    
    // 실행 결과 : THREE, FOUR, FIVE

2. valueOf()

  • valueOf() 메서드는 String 을 파라미터로 받는데 인자로 들어온 String 과 일치하는 상수 인스턴스가 존재하면 그 인스턴스를 반환한다. 이 또한 마찬가지로 단순히 문자열을 반환하는 것이 아니라 인자로 들어온 문자열과 일치하는 인스턴스를 반환하는 것이다.
    public static void main(String[] args) {
    		System.out.println(Rank.valueOf("THREE"));
    	}
    
    // 실행 결과 : THREE

3. ordinal()

  • Enum 클래스 내부에 있는 상수들의 Index 를 리턴하는 메소드이다. 배열과 마찬가지로 0부터 인덱스가 시작하며 인덱스의 length 는 상수의 수 - 1 이다.
    public static void main(String[] args) {
    		Rank[] values = Rank.values();
    		for(int i = 0; i< values.length; i++) {
    			System.out.println(values[i] + "인덱스는 : " + values[i].ordinal());
    		}
    	}
    
    // 실행 결과
    
    THREE인덱스는 : 0
    FOUR인덱스는 : 1
    FIVE인덱스는 : 2

사용 및 활용

단순히 위의 예시 만으로는 Enum의 효용에 대해 느끼기 힘든 것 같습니다. 물론 클래스에 상수가 많아 진다면Enum을 활용하는 것도 방법 이겠지만 구체적으로 어떠한 경우에서 사용해야 좋은 지에 대해서는 애매하다고 생각하였습니다. 그래서 인터넷에 있는 다양한 사례들을 아래에 정리 해 보았는데요, 사례 들을 보며 어떤 식으로 사용하면 좋을 지 본인이 판단하여 사용하시면 될 것 같습니다.

1. 데이터의 그룹화 및 관리에 용이

  • 관련되어 있지만 관련성을 표시하기 힘든 형태의 데이터를 한 곳에서 관리할 수 있다. 예를 들어 경기 이 후 승리한 사람과 패배한 사람을 리스트로 관리한다고 생각해보자. 이 경우 리스트의 변수명을 통해 관리하거나 Player 라는 클래스를 각기 상속 받는 형태로 관리하는 등의 방법으로 관리 될 수 있다. 하지만 Enum을 사용한다면 보다 명확한 방법으로 이들의 관계를 가시적으로 표현 할 수 있다.
    public enum  Winner {
    	WINNER("승리", Arrays.asList("kyle","pobi","hello","world")),
    	LOSER("패배", Arrays.asList("hodol","dunddoung","rutgo");
    
    	private final String winner;
    	private final List<String> list;
    
    	Winner(String winner, List<String> list) {
    		this.winner = winner;
    		this.list = list;
    	}
    }  
  • 단순히 관련 있는 데이터를 모아서 관리할 뿐만 아니라 자바에서 Enum은 완전한 클래스의 형태를 보이고 있기 때문에 관련 로직을 같은 Enum 클래스 내에서 관리할 수 있기 때문에 상태와 행위를 한 곳에서 관리 할 수 있다. 아래의 예들을 통해 승리자가 누구인지, 승리자가 몇 명인지 등 승리와 관련된 로직을 한 곳에서 관리할 수 있는 장점이 있다.
    public boolean isWinner(String name) {
    		return WINNER.list.contains(name);
    	}
    	
    	public int getWinnerSize() {
    		return WINNER.list.size();
    	}
  • 비슷한 예로 구매한 로또와 당첨번호가 같은 갯수, 그리고 각 갯수에 대응하는 상금 등과 같이 관련된 데이터도 하나의 Enum으로 관리 할 수 있다. 로또의 당첨 개수와 당첨 금액을 관리하는 하나의 클래스로 클래스를 분리하지 않고도 두 데이터(일치한 갯수와 당첨 금액)의 연관성을 명확하게 보여준다. 뿐 아니라 일치한 수를 입력하면 그 수와 일치하는 인스턴스를 반환하는 메소드 등 상태와 연관있는 행위 또한 한 곳에서 관리 할 수 있는 장점이 있다.
    public enum Statistic {
    	THREE(3, 5000),
    	FOUR(4, 50_000),
    	FIVE(5, 1_500_000),
    	BONUS(5, 3_000_000),
    	SIX(6, 2_000_000_000);
    
    	private final int matchingNumbers;
    	private final int prize;
    
    	Statistic(int matchingNumbers, int prize) {
    		this.matchingNumbers = matchingNumbers;
    		this.prize = prize;
    	}
    
    public static Statistic getRank(int numberOfMatch) {
    		return Arrays.stream(values())
    			.filter(statistic -> statistic.matchingNumbers == numberOfMatch)
    			.findFirst()
    			.orElseThrow(new IllegalArgumentException("일치하는 번호가 3미만입니다."))
    	}

2. Lambda를 활용한 Enum 사용 극대화

Enum은 자바의 익명함수 인터페이스 및 커스터마이징 익명 함수를 이용하면서 다양한 방법으로 더욱 활용 될 수 있다. 간단한 계산기 프로그램을 살펴보자.

    public interface Calculator {
    	int calculate(int a, int b);
    }
    
    class Plus implements Calculator{
    	@Override
    	public int calculate(int a, int b) {
    		return a + b;
    	}
    }
    
    class Minus implements Calculator{
    	@Override
    	public int calculate(int a, int b) {
    		return a - b;
    	}
    }
    
    class Multiply implements Calculator{
    	@Override
    	public int calculate(int a, int b) {
    		return a * b;
    	}
    }
    
    class Divide implements Calculator{
    	@Override
    	public int calculate(int a, int b) {
    		if(b == 0) 
    			throw new ArithmeticException();
    		return a / b;
    	}
    }
  • 계산을 위해 Calculator 라는 상위 인터페이스를 두고 더하기, 빼기, 곱하기, 나누기 부분이 각각 이를 구현하는 방식을 취하고 있다. 아래는 위의 코드를 Enum 으로 옮긴 코드이다. 물론 함수형 인터페이스 자체의 강점으로 인해 코드가 간결해진 부분도 있지만 더하기와 빼기 등 연산에 대한 명확한 상수명을 함께 가지면서 내부 계산 로직 또한 같이 가지고 있다는 점이 장점이다.
    
    import java.util.function.BiFunction;
    
    public enum Operator {
    	PLUS("더하기", (a, b) -> (a + b)),
    	MINUS("빼기", (a, b) -> (a - b)),
    	MULTIPLY("곱하기", (a, b) -> (a * b)),
    	DIVIDE("나누기", (a, b) -> (a / b));
    
    	private final String name;
    	private final BiFunction<Double, Double, Double> biFunction;
    
    	Operator(String name, BiFunction<Double, Double, Double> biFunction) {
    		this.name = name;
    		this.biFunction = biFunction;
    	}
    
    	public Double calculate(double a, double b) {
    		return this.biFunction.apply(a,b);
    	}
    }
  • Enum을 통해 기존에 고정되어 있는 특정 상수 혹은 Enum의 value 들에 대해서 관리하는 메소드 등으로 구성되어 있었다면 함수형 인터페이스를 통해 Enum의 각 상수 인스턴스도 조금 더 능동적은 프로그래밍이 가능해졌고, 상수와 함수식 및 정보를 같이 관리하기 때문에 상태와 행위 둘을 모두 같은 곳에서 관리한다는 점에서 장점을 가진다.

추가적인 장점

  • IDE의 적극적인 지원
    • 자동완성, 오타검증, 텍스트 리팩토링 등등
  • 허용하고자 하는 값으로 제한하기
  • 리팩토링시 변경 범위가 최소화 됩니다.
    • 상수를 Enum에서 관리하기 때문에 내용의 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없습니다.
  • 다른 언어와 달리 Java 에서는 Enum 이 완전한 기능을 가지고 있는 클래스라서 상수와 관련된 작업들을 추가 할 수 있다.

Effective Java

후기

Enum 또한 기존의 프로그래밍의 불편한 부분을 개선하기 위해 등장한 하나의 도구입니다. 기존에 클래스나 인터페이스를 통해 상수를 관리하던 것에서 Enum을 사용함으로써 IDE의 지원이나, 상태와 행위를 한 곳에서 관리한다. 등의 여러가지 장점을 가질 수 있지만 아직까지 어떠한 상황에서 Enum이 적합한지 잘 생각해 내는 것은 어려운 것 같습니다. 그래서 다음 포스팅에는 각 블로그에서 Enum을 사용했던 이야기들을 가져와서 사례를 다양하게 보면서 적절한 Enum의 사용에 대해 고민해 보는 시간을 갖도록 하겠습니다. 읽어주셔서 감사합니다.

3개의 댓글

comment-user-thumbnail
2021년 7월 5일

많은 도움이 되었습니다! Enum을 잘쓰면 매우 유용할거 같습니다.

답글 달기
comment-user-thumbnail
2022년 4월 1일

감사합니다.

답글 달기
comment-user-thumbnail
2022년 4월 3일

오홍 이넘이 이런 것이었군요ㅎㅎ 이해가 쏙쏙 잘되네요!

답글 달기