Java

[Java] enum 사용해보기

yooverd 2025. 3. 4. 17:43
728x90
반응형
SMALL

 

일을 하다보니 enum 이 정말 유용하고 편리하기 때문에 개발할 때 많이 사용하는 것 같은데, 정작 나는 enum의 필요성을 절실히 느껴본 적이 없었다.

아직 다양한 경험을 해보지 못하고, 필요한 경우 이미 다른 분들이 개발해주셔서  enum의 필요성을 크게 느끼지 못하는 것 같아 강제 경험주입을 해보고자 이런 저런 예시를 인터넷에서 찾아보기로 했다.

 


 

Enum 이란

Enum은 Enumeration의 약자이다.

네이버 영어사전에 검색해보면 '(하나하나)셈', '목록', '열거' 라는 뜻이 나온다. 과거 java 수업을 들을 때, 선생님 또한 enum 에 대해서 간단히 말하자면 상수(객체) 열거형 객체 라고 말해주신 적이 있다. 

어떤 변하지 않는 수많은 상수 중 묶어낼 수 있는 상수끼리 모아 열거하여 묶어놓은 것. 그것이 바로 enum 인 것 같다.

 

Enum의 장점

enum 을 활용하기에 앞서 장점을 알아둬야 올바르게 활용할 수 있을 것이다. enum의 장점엔 뭐가 있을까?

여러 블로그에서 enum 의 장점을 많이 나열해주셨는데, 내가 생각했을 때, 큰 장점으로 느껴진 것들은 다음과 같았다

1. type safe한 코드를 작성할 수 있으며 코드 유지보수에 큰 도움이 된다.

2. enum 의 각 상수는 싱글턴 객체와 동일한 방식으로 동작 하여 Thread Safe 하며 싱글턴 패턴 사용 시 도움이 된다.

 

 

Enum 예시

1. 상수의 공통적으로 갖고 있는 상태와 동작을 모아서 Enum으로 관리한다

Enum의 상수는 단순한 정수가 아닌 객차라는 것이야말로 Enum의 특징 중 하나라고 생각한다.

즉, Enum의 상수가 공통적으로 갖고있는 특성들, 그러나 상수마다 다르게 갖고 있는 값들을 모아서 관리할 수 있다.

 

예를 들어, {카페 메뉴의 음료} 를 enum 으로 만들어보면 다음과 같을 수 있겠다.

카페 메뉴에 알바생의 행위까지 들어있는 것이 조금 이상하긴 하지만, 추상메서드도 함께 사용해보고 싶어서 넣어보았다.

public enum CafeDrink {
    /*
     * name : AMERICANO, LATTE, CAMOMILE, ICETEA
     * 커피종류 : coffe,  tea,  ade
     * 할인율 : 0.05, 0.20, 0.10, 0
     * 동작 : 알바생의 이벤트 소개 및 가격 안내
     */

    // 추상함수를 사용하는 경우
    AMERICANO("coffee", 3000, 0.50){
        @Override
        public void doWork() {
            long realPrice = Math.round(AMERICANO.getPrice() * AMERICANO.discount);
            System.out.println("결제 도와드리겠습니다. " + AMERICANO.name() + "는 현재 할인 이벤트중입니다. 할인해서 " + realPrice + " 원 입니다.");
        }
    },
    LATTE("coffee", 3500, 0.20){
        @Override
        public void doWork() {
            System.out.println("어... 라떼는 할인 이벤트가 끝났습니다....! 죄송합니다." + LATTE.price + " 원 결제 도와드리겠습니다.");
        }
    },
    CAMOMILE("tea", 4000, 0.10) {
        @Override
        public void doWork() {
            System.out.println("캐모마일 티를 드신 당신!!! 무료 이벤트 당첨입니다!!! 축하드립니다~~ 안녕히 가세요~");
        }
    },
    ICETEA("ade", 2800, 0) {
        @Override
        public void doWork() {
            long realPrice = Math.round(AMERICANO.getPrice() * AMERICANO.discount);
            System.out.println("ade 는 할인 제외 대상입니다. " + ICETEA.price + "결제 도와드리겠습니다.");
        }
    },
    ;


    // CafeDrink 상태 필드
    private final String drinkType;
    private final long price;
    private final double discount;
    // CafeDrink 익명 메서드 (각 상수에서 오버라이드 시켜야함.)
    public abstract void doWork();

    // 생성자
    CafeDrink(String coffeeType, long price, double discount) {
        this.drinkType = coffeeType;
        this.price = price;
        this.discount = discount;
    }

    // 커피 타입 반환 메서드 (@Getter 적용 안됨.)
    public String getCoffeeType() { return drinkType; }
    
    // 커피 가격 메서드
    public long getPrice() { return price; }
    // 커피별 할인률 메서드
    public double getDiscount() { return discount; }

    // 제조 메서드
    public void make() {
        System.out.println(name() + "을/를 제조하겠습니다.");
    }
    // 제작 완료 안내 메서드
    public void receive() {
        System.out.println("주문하신 " + name() + "나왔습니다.");
    }
}

 

사용은 다음과 같이 해볼 수 있다.

    @PostMapping("test")
    public void test() {
        // 실행 테스트
        CafeDrink americano = CafeDrink.AMERICANO;
        System.out.println("타입: " + americano.getCoffeeType()); // 타입: coffee
        americano.make(); 
        americano.receive();
        CafeDrink.AMERICANO.doWork();.

        CafeDrink camomile = CafeDrink.CAMOMILE;
        System.out.println("타입: " + camomile.getCoffeeType()); // tea
        camomile.make();
        camomile.receive();
        CafeDrink.CAMOMILE.doWork();
        
                
        // 출력결과
        // 타입: coffee
        // AMERICANO을/를 제조하겠습니다.
        //주문하신 AMERICANO나왔습니다.
        // 결제 도와드리겠습니다. AMERICANO는 현재 할인 이벤트중입니다. 할인해서 1500 원 입니다
        // 타입: tea
        // CAMOMILE을/를 제조하겠습니다.
        // 주문하신 CAMOMILE나왔습니다.
        // 캐모마일 티를 드신 당신!!! 무료 이벤트 당첨입니다!!! 축하드립니다~~ 안녕히 가세요~
    }

 

 

인터페이스형 함수를 선언하여 사용할 수도 있다.

// 인터페이스형 함수 선언
@FunctionalInterface
interface CafeDrinkWorker {
    public void doWork(long price, double discount );
}

public enum CafeDrink_FunctionalInterface {
    /*
     * name : AMERICANO, LATTE, CAMOMILE, ICETEA
     * 커피종류 : coffe,  tea,  ade
     * 할인율 : 0.05, 0.20, 0.10, 0
     * 동작 : 가격 안내
     */

    // 파라미터로 인페이스형 함수를 익명함수로 넣는 방법. 간단한 메서드라면 람다식을 이용하여 예쁘게 만들 수 있음
    AMERICANO("coffee", 3000, 0.50, (long price, double discount) -> System.out.println("아메리카노의 가격은 " + Math.round(price*discount) + "입니다.") ),
    LATTE("coffee", 3500, 0.20, (long price, double discount) -> System.out.println("라떼의 가격은 " + Math.round(price*discount) + "입니다.")),
    CAMOMILE("tea", 4000, 0.10, (long price, double discount) -> System.out.println("캐모마일티의 가격은 " + Math.round(price*discount) + "입니다.")),
    ICETEA("ade", 2800, 0, (long price, double discount) -> System.out.println("아이스티의 가격은 " + Math.round(price*discount) + "입니다."));


    // CafeDrink 상태 필드
    private final String drinkType;
    private final long price;
    private final double discount;
    // 인터페이스형 함수 변수
    private final CafeDrinkWorker worker;

    // enum의 메서드. (알바생을 불러 일을 시킴)
    public void callWorker() {
        worker.doWork(price, discount);
    }

    // 생성자
    CafeDrink_FunctionalInterface(String coffeeType, long price, double discount, CafeDrinkWorker worker) {
        this.drinkType = coffeeType;
        this.price = price;
        this.discount = discount;
        this.worker = worker;
    }

    // 커피 타입 반환 메서드 (@Getter 적용 안됨.)
    public String getCoffeeType() { return drinkType; }
    // 커피 가격 메서드
    public long getPrice() { return price; }
    // 커피별 할인률 메서드
    public double getDiscount() { return discount; }

    // 제조 메서드
    public void make() { System.out.println(name() + "을/를 제조하겠습니다."); }
    // 제작 완료 안내 메서드
    public void receive() {
        System.out.println("주문하신 " + name() + "나왔습니다.");
    }
}

 

사용은 다음과 같이 한다

    @PostMapping("test")
    public void test() {
        // 실행 테스트
        CafeDrink_FunctionalInterface americano = CafeDrink_FunctionalInterface.AMERICANO;
        System.out.println("타입: " + americano.getCoffeeType()); // tea
        americano.make();
        americano.receive();
        americano.callWorker();

        CafeDrink_FunctionalInterface camomile = CafeDrink_FunctionalInterface.CAMOMILE;
        System.out.println("타입: " + camomile.getCoffeeType()); // tea
        camomile.make();
        camomile.receive();
        camomile.callWorker();
        
        // 출력결과
        //타입: coffee
        //AMERICANO을/를 제조하겠습니다.
        // 주문하신 AMERICANO나왔습니다.
        // 아메리카노의 가격은 1500입니다.
        // 타입: tea
        // CAMOMILE을/를 제조하겠습니다.
        // 주문하신 CAMOMILE나왔습니다.
        // 캐모마일티의 가격은 400입니다.
    }

 

Enum 의 실전 활용

상태와 행위를 묶어 한 곳에서 관리

행위에 대한 정의는 없지만 상태만 관리하는 경우

@Getter
public enum DataCode {
    PHOTO("PHO001"),
    GIF("PHO002"),
    MOVIE("MOV001"),
    EXCEL("FIL001"),
    WORD("FIL002");
    
    private final String codeValue;
    
    DataCode(String codeValue) {
    	this.codeValue = codeValue;
    }
}

 

행위에 대한 관리

public enum CuteTalk {
    NONE, A_LITTLE, VERY;

    public String changeStyle(String originalTak) {
        String resultTalk = "";
        switch (this) {
            case A_LITTLE:
                resultTalk = originalTak + "~!";
                break;
            case VERY:
                resultTalk = originalTak + "~! >_<";
                break;
            case NONE:
                resultTalk = originalTak;
                break;
        }
        return resultTalk;
    }

    public static void main(String[] args) {
        System.out.println(CuteTalk.NONE.changeStyle("hello"));
        System.out.println(CuteTalk.A_LITTLE.changeStyle("hello"));
        System.out.println(CuteTalk.VERY.changeStyle("hello"));
    }

 

 

Enum 실전 활용

그 밖의 다른 경험 많은 시니어 개발자 분들은 enum 을 어떻게 활용하였는지 찾아보았다.
참고한 주소는 아래 링크를 달아놓았다.

https://jojoldu.tistory.com/137

코드 관리용 테이블 대체

@Enumerated 아노테이션을 함께 활용하여 코드성 테이블을 enum 으로 대체하여 활용하는 방법이 있다.

데이터가 빈번히 수정되지 않는 코드성 데이터들을 enum으로 관리한다면, 다른 엔티티를 조회할 때, 매번 코드성 테이블을 join하지 않고 코드 단에서 그 값들을 손쉽게 확인할 수 있다.

 

타입별 다른 로직 처리

enum의 활용 방법을 찾아보다가 가장 매력적으로 느껴졌던 부분이었다.

예를 들어, 매출액을 통해 원금액, 공급가액, 부가세를 계산해야한다. 이 때, 각 금액의 종류를 enum으로 묶어 각 타입에 맞춰 로직이 지정되도록 할 수 있다!

별로 좋지 못한 예시였지만, 위에서 정의한 enum의  CafeDrink.AMERICANO.doWork(); 와 같은 느낌이다!

그러면 비즈니스로직 쪽에서 각 타입을 매번 확인하여 분기처리하지 않아도 되니, 코드가 더욱 깔끔해지고 각 동작의 책임이 더욱 올바르게 할당된 것 같은 느낌이 들 것 같다!

 

enum의 상수를 그룹화하여 enum!

enum 을 이미 하나 만들고 보니 그 enum을 다시 묶어서 관리할 수 있어 보이는 경우에 enum 배열을 갖고있는 enum을 만들어 활용할 수 있다.

사실 enum 예시 코드를 만들면서 각 DrinkType에 따라 다른 로직이 실행되도록 하고 싶었는데, enum 안에서 분기처리를 하자니 묘하게 코드가 지저분해지는 느낌이 들었다.

이 경우 해당 enum들을 그룹화한 enum을 만들어 사용했다면 더욱 명료하고 책임 관계가 확실한 코드로 작성할 수 있었을 것 같다. 그래서 다시 작성해봤다.

 

커피 할인율이 커피 종류별로 지정되어 있는 경우를 상상하며 만들었다.

public enum DrinkType {
    COFFEE(0.5, new CafeDrink[]{
            CafeDrink.AMERICANO, CafeDrink.LATTE
    } ),
    TEA(0.3, new CafeDrink[]{
            CafeDrink.CAMOMILE
    }),
    ADE(0.1, new CafeDrink[]{
            CafeDrink.ICETEA
    });

    @Getter
    private final double discount;
    @Getter
    private final CafeDrink[] containDrinks;

    DrinkType(double discount, CafeDrink[] containDrinks) {
        this.discount = discount;
        this.containDrinks = containDrinks;
    }

    public static DrinkType findDrinkType(CafeDrink drink) {
        return Arrays.stream(DrinkType.values())
                .filter(type -> hasDrink(type, drink))
                .findFirst()
                .orElse(null); // .orElse(CafeDrink.EMPTY)
    }

    public static boolean hasDrink(DrinkType type, CafeDrink drink) {
        return Arrays.stream(type.containDrinks)
                .anyMatch(containDrinks -> containDrinks == drink);
    }
}
    @GetMapping("/enum-test-enumgroup")
    public void enumTestEnumGroup() {
//        DrinkType americanoType = DrinkType.findDrinkType(CafeDrink.AMERICANO);
//        double discount = americanoType.getDiscount();
        System.out.println("커피 종류별 할인 이벤트 중입니다~ 아메리카노의 가격은 : " +
                CafeDrink.AMERICANO.getPrice() *
                        DrinkType.findDrinkType(CafeDrink.AMERICANO).getDiscount()
        + " 원 입니다.");
    }

 

역시나 억지로 코드를 만들어 본거라 그냥 보기엔 다소 지저분해 보이는 것 같다.

그러나 적절한 때에 적절하게 사용한다면 타당한 위치에서 로직이 관리되어 더욱 편리하게 사용할 수 있으며 가독성도 함께 따라와 줄 것이다.

 


 

참고

 

 

☕ 자바 Enum 열거형 타입 문법 & 응용 💯 정리

Enum 열거 타입 먼저 Enum은 "Enumeration"의 약자다. Enumeration은 "열거, 목록, 일람표" 라는 뜻을 가지고 있으며, 보통 한글로는 열거형이라고 부른다. 즉, 열거형(enum)은 요소, 멤버라 불리는 명명된 값

inpa.tistory.com

 

Enum 활용사례 3가지

안녕하세요? 이번 시간엔 enum 활용사례를 3가지정도 소개하려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세미나+

jojoldu.tistory.com

 

면접을 위한 CS 전공지식 노트 - 예스24

디자인 패턴, 네트워크, 운영체제, 데이터베이스, 자료 구조, 개발자 면접과 포트폴리오까지!CS 전공지식 습득과 면접 대비, 이 책 한 권이면 충분하다!개발자 면접에서 큰 비중을 차지하는 CS(Comp

iryan.kr

 

728x90
반응형
LIST