본문 바로가기
☕Java/Java 기본

enum 어떻게 잘 활용할까?

by 캔 2024. 5. 8.

 

자바 1.5 버전부터 도입된 enum은 여러 상수들을 한 곳에서 정의하기 위해 사용하는 타입이다. 벌써 자바 21 버전이 등장했지만 아직도 enum의 매력을 모르는 이들을 위해 enum을 소개하려고 한다. 먼저, enum의 특징에 대해 간단히 정리하고 활용법을 설명하겠다.

 

Enum의 특징

enum은 상수 이름, 멤버 변수, 생성자, 메서드로 구성된다.

public enum Sns {
    // 상수의 이름과 필드에 해당하는 값들
    NAVER("naver", "네이버"),
    KAKAO("kakao", "카카오"),
    GOOGLE("google", "구글"),
    APPLE("apple", "애플"),
    X("x", "트위터"),
    INSTAGRAM("instagram", "인스타그램");
    
    // 값을 담는 필드들
    private final String englishName;
    private final String koreanName;
    
    // 생성자(묵시적으로 private)
    Sns(final String englishName, final String koreanName) {
       this.englishName = englishName;
       this.koreanName = koreanName;
    }
    
    // 상수나 상수가 가지는 값들을 이용해서 메서드 정의 가능
    public String getEnglishName() {
       return englishName;
    }
    
    public String getKoreanName() {
       return koreanName;
    }
}

 

상수들은 각각 필드에 해당하는 값들을 가질 수 있으며, 상수 이름 뒤 소괄호에 필드의 순서대로 값을 나열한다.

 

enum type을 생성하는 방법은 다음과 같다

1. enum명.상수명

ex) Sns.KAKAO

2. enum명.valueOf(상수명)

ex) Sns.valueOf("KAKAO")

3. Enum.valueOf(enum명.class, 상수명)

ex) Enum.valueOf(Sns.class, "KAKAO")

 

enum 타입은 java.lang.Enum 클래스를 상속하며, 상수가 정의된 순서(0부터 시작)를 반환하는 ordinal(), 상수 이름 매개변수에 해당하는 상수를 반환하는 valueOf(String name), 상수의 이름을 반환하는 name(), 해당 enum의 모든 상수들을 담고 있는 배열을 반환하는 values() 메서드를 기본적으로 가지고 있다.

 

비교 시 값뿐만 아니라 타입 체크하여 상수 인터페이스보다 타입 안정성 높고, 변수와 메서드 사용이 가능하며, 상수를 재정의하더라도 그 상수를 참조하는 클래스들을 다시 컴파일하지 않아도 된다.

 

enum 간에 >, < 연산자는 사용할 수 없고, Comparable<T> 인터페이스를 구현하여 compareTo() 메서드를 사용해야 한다. 

 

추상 메서드를 추가하면 상수마다 구현 메서드를 추가할 수도 있다.

Enum 활용법

기본적으로 코드에 흩어진 문자열이나 숫자를 한데 모아서 관리할 때 사용한다. 하드 코딩된 문자열이나 상수, 매직 넘버들을 하나로 모아서 관리하면, 수정 발생 시 관리 포인트 줄일 수 있다. 값이 바뀌면 enum 상수 값만 바꿔주면 된다.

 

자바 1.8부터 등장한 함수형 인터페이스 덕분에 enum은 더욱더 강력해졌다. 변수에 람다식을 넣을 수 있기 때문에 로직이나 검증 규칙 등을 할당할 수 있게 되었다. 이 때문에 상수별로 달라지는 로직을 넣을 수 있고 이를 이용해 switch-case 문을 대체할 수도 있다.

 

switch-case 같이 로직 추가 및 수정 가능성이 높은 코드를 한 곳에 모아서 관리할 수 있어서 OCP(Open-Close Principle)를 지킬 수 있고 가독성도 높아진다.

 

실제 활용례를 한 번 살펴보자. 위의 코드는 SNS 로그인에 사용되는 SNS 종류를 담고 있다. 아래와 같이 롬복 라이브러리 애너테이션(@RequiredArgsConstructor, @Getter)를 사용하면 생성자와 getter를 줄일 수 있다. 변수와 값만 남아서 가독성이 좋아진다. 여기에 추가적으로 메서드를 만들어 사용할 수 있다.

@RequiredArgsConstructor
@Getter
public enum Sns {
    // 상수의 이름과 변수에 해당하는 값들
    NAVER("naver", "네이버"),
    KAKAO("kakao", "카카오"),
    GOOGLE("google", "구글"),
    APPLE("apple", "애플"),
    X("x", "트위터"),
    INSTAGRAM("instagram", "인스타그램");

    // 값을 담는 변수들
    private final String englishName;
    private final String koreanName;
}

 

여기에 위에서 말했듯이 람다식을 활용하여 로직을 변수로 추가하면 로직을 담은 switch-case 문을 대체할 수 있다.

 

@RequiredArgsConstructor
@Getter
public enum Sns {
    // 상수의 이름과 변수에 해당하는 값들
    NAVER("naver", "네이버", (baseUrl, config) -> {
        UriComponents urlComponent = UriComponentsBuilder.fromUriString("http://api.naver.com/v1/api/authentication")
                .queryParam("api_key", config.getKeyValue("naver_api_key"))
                .queryParam("api_secret", config.getKeyValue("naver_api_secret"))
                .queryParam("returnUrl", baseUrl)
                .build();
        return urlComponent.toString();
    }),
    KAKAO("kakao", "카카오", (baseUrl, config) -> {
        UriComponents urlComponent = UriComponentsBuilder.fromUriString("http://api.kakao.com/v1/api/authentication")
                .queryParam("api_key", config.getKeyValue("kakao_api_key"))
                .queryParam("api_secret", config.getKeyValue("kakao_api_secret"))
                .queryParam("returnUrl", baseUrl)
                .build();
        return urlComponent.toString();
    }),
    // 상수의 이름과 변수에 해당하는 값들
    GOOGLE("google", "구글", (baseUrl, config) -> {
        // 구글 SNS 로그인 URL 로직
    }),
    APPLE("apple", "애플", (baseUrl, config) -> {
        // 애플 SNS 로그인 URL 로직
    }),
    X("x", "트위터", (baseUrl, config) -> {
        // 트위터 SNS 로그인 URL 로직
    }),
    INSTAGRAM("instagram", "인스타그램", (baseUrl, config) -> {
        // 인스타그램 SNS 로그인 URL 로직
    });

    // 값을 담는 변수들
    private final String englishName;
    private final String koreanName;
    private final BiFunction<String, Config, String> urlFetchLogic;

    // 메서드
    public String fetchUrl(String baseUrl, Config config) {
        return this.urlFetchLogic.apply(baseUrl, config);
    }
}

 

만약에 SNS 종류가 늘어나서 SNS 종류와 그에 해당하는 URL 가져오기 로직을 추가해야 한다면 이 enum을 수정하는 것만으로 해결이 가능하다.

 

참고

enum에 람다식을 사용한 사례는 우아한형제들 테크블로그와 이동욱(향로)님의 블로그에도 나와있으니 참고해 보길 바란다.