본문 바로가기
시리즈/자바

[JAVA] Enum 열거 타입에 대해 알아보기 ②

by 되고싶은노력가 2024. 10. 31.

Enum 고급 문법.

 

Enum 매핑

enum 에 열거된 상수에는 특정 값을 매핑할 수 있다.

아래 예시는 로또 등수에 맞게 맞힌 갯수와 그에 따른 금액을 매핑한 것을 볼 수 있다. 한 개만 가능한 것이 아닌 여러 정보를 매핑할 수 있으며 매핑 하기 위해선 필드를 선언하고 그에 맞는 생성자를 선언하면 된다.

public enum Rank {
    FIRST_PLACE(6, 2000000000),
    SECOND_PLACE(5, 30000000),
    THIRD_PLACE(5, 1500000),
    FOURTH_PLACE(4, 50000),
    FIFTH_PLACE(3, 5000),
    NOTHING(0, 0);
    
    private int matchedCount;
    private long winningAmount;

    Rank(int matchedCount, long winningAmount) {
        this.matchedCount = matchedCount;
        this.winningAmount = winningAmount;
    }
}

 

Enum 의 데이터 그룹화와 관리

데이터들이 서로 관련되어 있지만 관련된 형태를 구현하는데 있어 애로 사항이 생긴다면 enum 을 통해 한 클래스 내에서 관리할 수 있게 된다.

 

예를 들어 체크 카드를 다룬다고 한다면, 카드사 마다 다르며 신한 카드를 쓰더라도 여러가지 카드 종류가 있을텐데, 본래라면 '카드사' 클래스와 '카드 종류' 클래스로 두개로 나눠 코드를 구성해야 하지만 enum 을 이용하면 쉽게 관계를 가시적으로 표현할 수 있다.

 

또한 메서드 로직을 enum 객체 안에 구현해준다면 강력한 상수 클래스를 구현할 수 있게 된다.

enum CreditCard {

    SHINHAN("신한", Arrays.asList("Mr.Life 카드", "Deep Dream 카드", "Deep Oil 카드")),
    KB("국민", Arrays.asList("톡톡D 카드", "티타늄 카드", "다담 카드")),
    NH("농협", Arrays.asList("올바른 FLEX 카드", "테이크 5 카드", "NH 올원 파이카드"));

    private final String Enterprise;
    private final List<String> cards;

    CreditCard(String name, List<String> cards) {
        this.Enterprise = name;
        this.cards = cards;
    }
	
    String getCard(String cardName) {
        return Arrays.stream(CreditCard.values())
                .filter(creditCard -> creditCard.equals(cardName))
                .findFirst()
                .orElseThrow(Exception::new);
    }
} 

 

 

Enum 확장

enum 매핑 기능을 확장하여, enum 을 단순히 상수 값을 넘어서 상수 메서드로서도 이용이 가능하다.

 

추상 메서드를 정의해서 각 상수마다 익명 클래스처럼 메서드 재정의를 통해 각 상수마다 다른 역할을 하는 메서드를 갖게 되는 원리이다.

enum Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    MULTI("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };

    // 클래스 생성자와 멤버
    private final String symbol;
    
    Operation(String symbol) {
        this.symbol = symbol;
    }

    // toString을 재정의하여 열거 객체의 매핑된 문자열을 반환하도록
    @Override
    public String toString() {
        return symbol;
    }

    // 열거 객체의 메소드에 사용될 추상 메소드 정의
    public abstract double apply(double x, double y);
}

 

여기서 더 나아가 람다 표현식을 통해 더 깔끔한 코드를 만들어 낼 수 있다.

enum Operation {
    PLUS("+", (x, y) -> x + y),
    MINUS("-", (x, y) -> x - y),
    TIMES("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);

    private final DoubleBinaryOperator op; // 람다식을 저장할 필드

    private final String symbol;

    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

    @Override
    public String toString() { return symbol; }

    public double apply(double x, double y) {
        return op.applyAsDouble(x, y);
    }
}

Enum 적용하기.

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        this.numbers = numbers;
    }

    public Rank getRank(List<Integer> winningNumbers, int bonusNumber) {
        final int matchedCount = getMatchedCount(winningNumbers);
        final boolean isBonus = hasBonusNumber(bonusNumber);
        return Rank.findRank(matchedCount, isBonus);
    }

    private int getMatchedCount(List<Integer> winningNumbers) {
        HashSet<Integer> compareNumbers = new HashSet<>(numbers);
        HashSet<Integer> compareWinning = new HashSet<>(winningNumbers);

        compareNumbers.retainAll(compareWinning);
        return compareNumbers.size();
    }

    private boolean hasBonusNumber(int bonusNumber) {
        return numbers.contains(bonusNumber);
    }
}

 

로또의 순위를 관리하기 위해선 어떻게 해야할까? 본래라면 6개를 맞추면 1등이라는 정보와 1등이 받는 금액을 따로 if 문으로 반환해야 했을 것이다.

 

하지만 이를 enum 을 사용해서 관계있는 값을 묶고 enum 안에 메서드를 선언함으로써 확장성과 객체를 의미 있게 관리할 수 있게 된다.

package domain;

import java.util.Arrays;

public enum Rank {
    FIRST_PLACE(6, 2000000000),
    SECOND_PLACE(5, 30000000),
    THIRD_PLACE(5, 1500000),
    FOURTH_PLACE(4, 50000),
    FIFTH_PLACE(3, 5000),
    NOTHING(0, 0);

    private int matchedCount;
    private long winningAmount;

    Rank(int matchedCount, long winningAmount) {
        this.matchedCount = matchedCount;
        this.winningAmount = winningAmount;
    }

    public static Rank findRank(int matchedCount, boolean isBonus) {
        Rank find = Arrays.stream(Rank.values())
                .filter(rank -> rank.matchedCount == matchedCount)
                .findFirst()
                .orElse(NOTHING);

        if (find == Rank.SECOND_PLACE && !isBonus) {
            return Rank.THIRD_PLACE;
        }

        return find;
    }

    public long getWinningAmount() {
        return winningAmount;
    }

}

 

맞힌 갯수와 보너스 번호를 맞춘 여부에 따라 그에 맞는 Rank 를 반환 받게 되고 반환받은 Rank 객체에서 getWinningAmount 메서드를 통해 당첨금을 확인할 수 있게 된다.

 

그 외에도 단순한 상수를 관리도 가능하다.

public enum ViewMessage {
    ENTER_PURCHASE_AMOUNT_MESSAGE("구입 금액을 입력해 주세요."),
    ENTER_WINNING_NUMBER_MESSAGE("당첨 번호를 입력해 주세요."),
    ENTER_BONUS_NUMBER_MESSAGE("보너스 번호를 입력해 주세요."),
    PURCHASE_CONFIRMATION_MESSAGE("%d개를 구매했습니다."),
    WINNING_STATISTIC_MESSAGE("당첨 통계\n---"),
    WINNING_DETAIL_MESSAGE("%d개 일치 (%d원) - %d개"),
    WINNING_BONUS_DETAIL_MESSAGE("%d개 일치, 보너스 볼 일치 (%d원) - %d개"),
    TOTAL_REVENUE_RATE_MESSAGE("총 수익률은 %f%입니다.");

    private final String message;

    ViewMessage(String message) {
        this.message = message;
    }

    public String format(Object... args) {
        return String.format(message, args);
    }

    @Override
    public String toString() {
        return message;
    }
}

 


마치며.

enum 을 통해 단순한 상수 표현식을 넘어 동작, 행위를 수행하는 상수로 확장이 가능하다는 것과 객체에서 중요한 상수 상태와 행위를 한 곳에서 관리한 다는 점에서 매우 유용한 타입이라는 것을 알 수 있다. 추후 Lotto 기능 구현이 늘어남에 따라 적용하기 부분이 업데이트 될 것 같습니다.