티스토리 뷰

광고 클릭, 하트, 댓글은 필자에게 큰 힘이 됩니다!

오늘은 자바 열거형 타입 (Enum Type)에 대한 글을 준비했습니다. 자바를 알고 계시고 활용하고 계신 분들이라면 개념은 익히 알고 계시겠지만 어떨 때 활용하면 좋을지, 그리고 실제 프로젝트에 녹여낼 때 고민이 되었던 점들과 그 문제들을 어떻게 해결했는지 등등 제 경험을 토대로 이야기를 진행해 보겠습니다. 참, 이번 포스팅은 Java 8 기준으로 설명합니다.

 

우선 개념부터 짚고 넘어가볼까요?

 

#1> 정의 / Enum Type이란?


java.lang.Enum은 형식을 제한한 보다 안전한 상수 목록을 제공하기 위해 Java 1.5부터 추가되었습니다. Java의 Enum은 각 인스턴스가 속성과 로직을 구현할 수 있는 완전한 객체라는 점에서 다른 언어의 열거형 구조보다 유연합니다. 그런데, 왜 Class도 아니고 원시 타입(int, double 등)도 아닌 Enum Type이라고 부를까요? Enum Class가 따로 있는데 말이죠.

 

Enum Class의 정의

그 이유는 바로 enum instance는 암시적으로 Enum Class를 확장하고, final이며 generics를 사용해 정의할 수 없기 때문입니다. 다시 말해 개발자가 직접 Enum Class를 확장한 Class를 구현할 수 없는 형태로 설계되었습니다. 실제로 아래와 같이 Class를 정의한 뒤에 컴파일을 해보면 "classes cannot directly extend java.lang.Enum"이라는 에러가 발생합니다.

 

Enum Class를 확장하는 경우 발생하는 에러

이와 같은 이유로 열거형 인스턴스는 Enum Type이라고 불립니다. Class를 확장하지 못하면 열거형 타입(Enum Type)은 어떻게 정의하는 걸까요? 그 답은 JLS(Java Language Specification)에 나와있습니다.

Enum Type에 대한 설명

JLS 8.9 Section을 보면 Enum Type 선언 규칙과 더불어 주의사항에 대해 언급하고 있습니다. 위 문서에 나와있는 내용과 간단한 예제로 Enum Type 선언에 대한 설명을 이어나가 보겠습니다.

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}
EnumDeclaration Description Example
ClassModifier (option) public, protected, private, abstract, static, final, strictfp 중 하나 public
enum enum type을 나타내는 키워드 enum
Identifier enum instance의 이름 Day
Superinterface Superinterface 해당사항 없음
EnumBody

EnumConstantList와 EnumBodyDeclarations (option)

SUNDAY ... SATURDAY

위에 작성한 코드는 아주 흔하게 열거형 타입 예제로 사용되는 월-화-수-목-금-토-일 요일에 대한 열거형 데이터를 정의하는 코드입니다. 아주 간단한 코드이지만 테이블 내용을 참고해서 같이 살펴볼까요?

 

  1. 우선 public ClassModifier를 통해 이 Enum Type Instance가 외부에서 접근이 가능하다는 것을 명시하고 있습니다.
  2. 그리고 enum 키워드를 통해 enum type을 정의한다고 알려주고 있네요.
  3. 이어서 enum type의 이름은 Day라고 정의하고 있습니다.
  4. 중괄호 안에 comma로 구분된 코드들은 EnumConstantList를 나타내고 있습니다.

크게 어렵지 않으실 겁니다. 근데 예제에서 사용하지 않은, EnumBody의 설명에 옵션이라고 나와있는 EnumBodyDeclarations라는 녀석은 어떨 때 쓰는 걸까요? 사실 이 부분이 Enum Type의 핵심입니다. 서두에 말씀드렸던 Java의 Enum은 각 인스턴스가 속성과 로직을 구현할 수 있는 완전한 객체라고 할 수 있는 이유가 바로 EnumBodyDeclarations에 있습니다. 

 

Enum Type 은 공통 속성을 가진 상수들(Constants)의 집합을 말한다. 그리고 Java의 Enum은 각 속성과 로직을 구현할 수 있는 완전한 객체이다.

 

#2 > Enum Type의 활용


프로그래밍을 하다 보면 비슷한 속성을 가진 상수(Constant) 데이터를 관리해야 하는 경우가 발생합니다. 이럴 때 Enum Type을 활용하면 실수를 줄여주고, 읽기 쉬운 코드 작성이 가능합니다. 어떻게 이런 게 가능한지 예제와 함께 설명해 보겠습니다.

 

비교 가능한 값의 제한

사용자가 설정한 해상도에 따른 분기 처리를 해야 하는 경우를 생각해봅시다. 선택 가능한 해상도를 UHD, FHD, HD 3가지로 제한한다고 할 때 아래와 같이 Enum Type을 선언할 수 있습니다.

public enum Resolution {UHD, FHD, HD}

 

그리고 선언한 enum을 활용해 해상도에 따른 분기 처리가 가능합니다.

Resolution resolution = DBManager.getDefaultResolution();
switch (resolution) {
    case UHD:
        setPreviewResolution(3840, 2160);
        break;
    case FHD:
        setPreviewResolution(1920, 1080);
        break;
    case HD:
        setPreviewResolution(1280, 720);
        break;
    default:
        throw new IllegalStateException("Unexpected value: " + resolution);
}

 

우선 여기서 Enum Type의 장점을 하나 말씀드리겠습니다. switch 조건문에 비교 값으로 Enum Type을 설정하면 내부 case 구현 시 아래와 같이 IDE의 assistance가 Enum Type에 선언된 값 목록을 알려줍니다.

Enum Type에 정의된, Case가 될 수 있는 값을 친절하게 알려주는 모습

이처럼 Enum Type을 사용하면 조건문의 case를 정의한 상수로만 제한할 수 있습니다. 이렇게 사용자 입력과 그에 따른 처리를 제한함으로써 개발자는 예외상황을 보다 잘 컨트롤할 수 있습니다.

 

 

속성을 가진 객체로서의 Enum > 속성 부여하기

위 코드를 살펴보면 한 가지 아쉬운 점이 보입니다. 바로 해상도 이름과 그에 따른 width, height 값이 묶여있지 않다는 것인데요. 조금 더 풀어서 설명드리자면 UHD는 3840 x 2160의 해상도를 가지고 FHD는 1920 x 1080을, HD는 1280 x 720의 해상도를 가지지만 해상도의 이름에 따라 분기 처리를 통해 preview 해상도 값을 설정할 뿐 이름과 해상도 값 사이에는 어떠한 연관성도 없습니다.

 

이런 분기 처리가 한 곳에만 사용된다면 크게 불편하지 않겠지만 덩치가 큰 프로그램에서는 많은 Class에서 사용될 수 있습니다. 이럴 때 특정 값이 변하게 되면 그 코드가 사용된 로직을 모두 변경해야 한다는 번거로움이 발생하고, 이는 결국 예상치 못한 버그로 이어질 확률이 높습니다.

4군대 동일한 로직 중 한 곳에서 다른 해상도 값이 들어가도 컴파일 시 에러가 발생하지 않는다. 해상도 이름과 실제 해상도 값 사이에 연관성이 없기 때문.

이럴 때 아래와 같이 static 상수로 해상도 값들을 선언해 위와 같은 오류를 방지할 수 있겠지만, 해상도 이름과 실제 해상도 값 사이에 연관성이 없다는 점에서 여전히 아쉬움이 남습니다.

public static fianl UHD_RESOLUTION_WIDHT = 3840;
public static fianl UHD_RESOLUTION_HEIGHT = 2160;
public static fianl FHD_RESOLUTION_WIDHT = 1920;
public static fianl FHD_RESOLUTION_HEIGHT = 1080;
public static fianl HD_RESOLUTION_WIDHT = 1280;
public static fianl HD_RESOLUTION_HEIGHT = 720;

이럴 때 Java를 잘 이해하고 있는 개발자라면 #1 챕터에서 두 번이나 언급했던 Enum은 속성을 가질 수 있는 완전한 객체라는 점을 활용할 수 있습니다. 바로 아래처럼요.

public enum Resolution {
    UHD(3840, 2160), FHD(1920, 1080), HD(1280, 720);

    private int width, height;

    Resolution(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }
}

위 코드에서는 Enum Type의 생성자 매개변수로 실제 해상도 값을 설정해주고 있습니다. 그리고 getter를 통해 실제 해상도 값을 참조할 수 있습니다. 이제 해상도와 실제 해상도 값 사이에 연관성이 생겼기 때문에 기존 분기 처리를 보다 직관적인 형태로 변경할 수 있습니다.

Resolution resolution = DBManager.getDefaultResolution();
setPreviewResolution(resolution.getWidth(), resolution.getHeight());

어떤가요? 기존 13줄에 걸쳐 구현해야 했던 처리를 이렇게 한 줄로 나타낼 수 있습니다. 뿐만 아니라 동일한 로직이 여러 군대에서 쓰일 때에도 값이 서로 다르게 들어갈지 염려할 필요가 없어졌습니다.

츠암~~ 쉽죠?!.jpeg

Enum은 완전한 객체라는 말이 슬슬 이해가 되시나요? 이 녀석에 대해 조금 더 살펴보도록 하겠습니다.

 

 

속성과 로직을 구현할 수 있는 완전한 객체 Enum > DB 연동하기

지금까지 우리는 사용자가 선택한 해상도 값에 따른 처리에 대해 살펴보았습니다. 그런데 마지막으로 사용자가 설정한 값을 DB에 저장하고 사용자가 다시 앱을 사용할 때 그 값을 불러와야 한다면 어떻게 해야 할까요? 새로운 데이터나 맵을 사용하지 않고 Enum Type만을 사용해서 이런 처리가 가능할까요?

롸?.gif , 출처: http://app.jjalbang.today/view/%EB%8F%99%EA%B3%B5%EC%A7%80%EC%A7%84/6345

당황하지 않으셔도 됩니다. Enum은 로직을 구현할 수 있는 완전한 객체이기 때문이죠. 어떻게 구현이 가능한지 설명하기에 앞서 Enum Class의 Method에 대해서 간단히 살펴보겠습니다.

Enum Class에 정의된 메서드 목록

Enum Type이라면서 왠 Enum Class를 보냐구요? 포스팅 서두에서 말씀드렸다시피 enum instance는 암시적으로 Enum Class를 확장하기 때문에 위 메서드들을 사용할 수 있습니다. 여기서 name 메서드와 valueOf 메서드를 통해서 마지막으로 사용자가 선택한 enum constant를 저장하고 불러올 수 있습니다.

public enum Resolution {
    UHD(3840, 2160), FHD(1920, 1080), HD(1280, 720);

    private int width, height;

    public static Resolution toResolution(String enumConstantName) {
        try {
            return valueOf(enumConstantName);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return FHD;
        }
    }

    Resolution(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }
}

기존 코드에 toResolution 메서드를 추가했습니다. 이 메서드는 enum constant (열거형 상수) 이름을 입력받아 해당 이름과 매칭 되는 실제 enum type을 리턴합니다. 만약, 파라미터로 받은 enumConstansName이 enum type에 명시되지 않은 이름인 경우 default 값으로 FHD 객체를 리턴합니다. 이 메서드를 활용하면 DB에 name 메서드를 통해 이름만 저장하고, 이름을 통해 다시 enum type 객체를 사용할 수 있습니다.

 

마지막으로 실제 테스트 코드와 결과를 보여드리고 마치도록 하겠습니다.

public enum Resolution {
    UHD(3840, 2160), FHD(1920, 1080), HD(1280, 720);

    private int width, height;

    public static Resolution toResolution(String enumConstantName) {
        try {
            return valueOf(enumConstantName);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return FHD;
        }
    }

    Resolution(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }


    @Override
    public String toString() {
        return "Resolution{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}
public class Main {

    public static void main(String[] args) {
        Resolution resolution = Resolution.toResolution("UHD");
        System.out.println(resolution.name() + " " + resolution.toString());
        resolution = Resolution.toResolution("FHD");
        System.out.println(resolution.name() + " " + resolution.toString());
        resolution = Resolution.toResolution("HD");
        System.out.println(resolution.name() + " " + resolution.toString());
        resolution = Resolution.toResolution("테스트!");
        System.out.println(resolution.name() + " " + resolution.toString());
    }
}

"테스트!" 입력으로 인해 IllegalArgumentException이 발생했지만, FHD enum constant가 리턴된 결과를 확인 할 수 있다.

 

 

 

#3 > 마치며


삼항연산자에 이어 대부분 알고 있지만 정확히 알고있지 못하는 주제로 글을 써봤습니다. 이번 포스팅을 작성하면서 새롭게 알게된 부분도 있어 필자인 저에게도 유익한 시간이었습니다. 저 뿐만 아니라 이 글을 읽으시는 분들에게도 많은 도움이 되었으면 좋겠네요. 긴 글 읽어주셔서 감사합니다. 좋은 하루 되십시오.

 


광고 클릭, 하트, 댓글은 필자에게 큰 힘이 됩니다!

댓글