[Java] Enum 활용하기

청포도봉봉이·2024년 2월 11일
1

java

목록 보기
12/20
post-thumbnail

Enum이란

컴퓨터 프로그래밍에서 열거형(enumerated type, enumeration), 이넘(enum), 팩터(factor ← R 프로그래밍 언어와 통계학의 범주형 변수에서 부르는 명칭)는 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다.

참고
https://ko.wikipedia.org/wiki/%EC%97%B4%EA%B1%B0%ED%98%95

정수 열거 패턴(int enum pattern)

저는 보통 Enum을 사용할 때는 고정된 상수 등을 비즈니스 로직에 선언하는 것이 아닌 클래스화 하여 사용하는 것이라 이해했습니다.

그래서 과거에는

public static final int APPLE_PRICE = 0;
public static final int APPLE_COLOR = 0;
public static final int ORANGE_PRICE = 0;
public static final int ORANGE_COLOR = 0;

위와 방식을 정수 열거 패턴(int enum pattern)이라고 하는데, 보기만해도 단점이 많아 보입니다. 먼저 타입 안전성을 보장하기가 어렵습니다. 사과와 오렌지를 비교하는 메소드를 만들면 어떻게 될까요? 동등 연산자(==)로 비교해도 아무런 경고없이 동작하게 되겠지요.

그리고 표현 방식이 참 애매합니다. 사과용 상수와 오렌지용 상수의 이름 충돌을 방지하기 위해 접두사(prefix)를 사용했습니다. 마지막으로 이를 문자열로 출력하기도 다소 까다로운 점이 있습니다.

열거 타입(enum type)

그렇다면 열거 타입(enum type)을 사용하면 어떻게 될까요?

public enum Apple {
    PRICE, COLOR
}
public enum Orange {
    PRICE, COLOR
}

장점

그렇다면 열거 타입에는 어떠한 장점들이 있을까요?

  • 문자열과 비교하여, IDE 적극적인 지원을 받을 수 있습니다.
    • 자동완성, 오타검증, 텍스트 리팩토링 등등
  • 허용 가능한 값들을 제한할 수 있습니다.
  • 리팩토링시 변경 범위가 최소화 됩니다.
    • 내용의 추가가 필요하더라도 Enum 클래스만 수정해주면 됩니다.
  • 자바의 열거 타입은 완전한 형태의 클래스라고 볼 수 있습니다.
  • 열거 타입은 밖에서 접근할 수 있는 생성자를 제공하지 않으므로 사실상 final 이라고 볼 수 있습니다.
  • 인스턴스들은 오직 하나만 존재합니다.
  • 열거 타입은 컴파일 시에 타입 안전성을 제공합니다.
  • 열거 타입의 toString 메서드는 출력하기에 적합한 문자열을 제공합니다.
  • 임의의 메서드나 필드를 추가할 수 있고 임의의 인터페이스를 구현하게 할 수 있습니다.

예제

이제 예제로 장점을 살펴보겠습니다.

1. 데이터들 간의 연관관계 표현

만약에 Y(String), 1(int), true(boolean) 3가지 타입의 값이 같은 경우 보통 소스를 어떻게 짤까요?

public String getTable1Value(int originalValue) {
    if (originalValue == 1) {
        return "Y";
    }
    else {
        return "N";
    }
}

public boolean getTable2Value(String originalValue) {
    if (originalValue.equals("Y")) {
        return true;
    }
    else {
        return false;
    }
}

위의 코드는 테이블1, 테이블2이 존재할때 Y, 1, true의 값이 모두 같다고 사용할때 변환하는 과정입니다. 실제 업무할때 테이블 설계가 다르기 때문에 이런 경우가 있긴 하죠?

위 코드는 문제가 기능상 문제는 없지만, 몇가지 문제가 있습니다.

  • Y, 1, true가 모두 같은 의미기 때문에 메소드를 매번 살펴가며 어떤 것이 같은 의미인지 확인해줘야 합니다.
  • 리팩토링하기 어렵습니다.
    • 새로운 데이터 타입이 추가된다면 T, F라는 데이터가 추가된다면 일일히 메서드에 if else문을 추가해줘야 할 것입니다.

그래서 이 부분을 Enum 클래스로 변경한다면

public enum ValueEnum {
    Y(1, true),
    N(0, false);

    private int table1Value;
    private boolean table2Value;

    ValueEnum(int value, boolean flag) {
        this.table1Value = value;
        this.table2Value = flag;
    }

    public int getTable1Value() {
        return table1Value;
    }

    public boolean isTable2Value() {
        return table2Value;
    }
}

Y, 1, true, N, 0, false가 한묶음으로 됐다는걸 확인할 수 있습니다.

public class Main {
    public static void main(String args[]) {
        ValueEnum valueEnumY = ValueEnum.Y;
        ValueEnum valueEnumN = ValueEnum.N;

        System.out.println("fruitEnumY = " + valueEnumY.getTable1Value() + " " + valueEnumY.isTable2Value());
        System.out.println("fruitEnumN = " + valueEnumN.getTable1Value() + " " + valueEnumN.isTable2Value());
    }
}

ValueEnum enum 클래스를 선언해주면 바로 table1, table2가 어떤 데이터인지 확인할 수 있습니다.

2. 상태와 행위를 한곳에서 관리

예를 들어 DB에 저장된 값이 A일때, B일때 나머지 일때의 로직이 다른 경우가 있습니다.

public class Calculator {
    public static long start(String str, long value) {
        if (str.equals("A")) {
            return value * 2;
        }
        else if (str.equals("B")) {
            return value - 10;
        }
        else {
            return 0;
        }
    }
}

원래 저였다면 static 키워드를 활용하여 사용했을 겁니다.

Table table1 = new Table("A");
String str = table1.getData();
long value = 5L;
long result = Calculator.start(str, value);
System.out.println("result = " + result);

이런 상황에서 문제가 있는게 Table에서 데이터를 가져오고 그 데이터를 또 별도의 메소드를 활용하여 진행한다는 것이였습니다.

  • Table 클래스와 start 메소드와의 연관관계를 설명하기 힘듭니다.
  • 똑같은 기능을 하는 메소드를 중복 생성할 수 있습니다.
    • 히스토리 관리가 안된 상태라면 계산 메소드가 있다는 사실을 몰라 다시 만드는 경우가 빈번합니다.
    • 기존 화면의 로직이 변경되는 경우, 신규 인력은 2개의 메소드를 전부 변경해야 하는지 해당 화면만 수정해야하는지 모릅니다.

따라서 Enum 클래스를 활용하여 DB에서 가져온 특정 값을 지정된 메소드와 연관관계가 있다. 즉, 특정 데이터와 메서드를 한꺼번에 실행 할 수 있게 처리하였습니다.

함수형 인터페이스를 활용하였습니다.

public enum CalculatorEnum {
    CAL_A(value -> value * 3),
    CAL_B(value -> value * 10);

    private Function<Long, Long> expression;

    CalculatorEnum(Function<Long, Long> expression) {
        this.expression = expression;
    }

    public long calculate(long value) {
        return expression.apply(value);
    }
}

값이 A 또는 B일때 실행하는 메소드까지 묶었습니다.

long value2 = 13L;
CalculatorEnum calA = getCalA();
System.out.println("result2 = " + calA.calculate(value2));

CalculatorEnum calB = getCalB();
System.out.println("result3 = " + calB.calculate(value2));

위와 같이 getCalA()라는 메소드를 통해 데이터를 바로 CalculateEnum 타입으로 변환 후 calculate를 실행할 수 있습니다.

그리고 실제로 사용하는 곳에서도 이젠 직접 Code에게 계산을 요청하면 됩니다.

값(상태)과 메소드(행위)가 어떤 관계가 있는지에 대해 더이상 다른 곳을 찾을 필요가 없게 되었습니다.
코드내에 전부 표현되어 있고, Enum 상수에게 직접 물어보면 되기 때문입니다.

정리


Java Enum을 활용하면

  • 데이터들 간의 연관관계 표현
  • 상태와 행위를 한곳에서 관리

즉 상수의 값들을 좀 더 효율적으로 관리하여 가독성과 리팩토링에 도움이 된다는 걸 알 수 있습니다. 이렇게 정리한 내용을 바탕으로 Enum을 적극적으로 활용하도록 고민해야겠다.

이 글은 학습을 위해
https://techblog.woowahan.com/2527/
https://madplay.github.io/post/use-enums-instead-of-int-constants#google_vignette
읽고 정리한 글입니다.

profile
서버 백엔드 개발자

0개의 댓글