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 기능 구현이 늘어남에 따라 적용하기 부분이 업데이트 될 것 같습니다.
'시리즈 > 자바' 카테고리의 다른 글
[JAVA] Enum 열거 타입에 대해 알아보기 ① (0) | 2024.10.29 |
---|---|
[JAVA] static 과 final 의 의미와 관계 (0) | 2024.10.27 |
[TEST] 테스트를 위한 JUnit 5 와 AssertJ ② (0) | 2024.10.25 |
[TEST] 테스트를 위한 JUnit 5 와 AssertJ ① (1) | 2024.10.24 |
[JAVA] 몰라서 못 썼던 정규 표현식을 알아보자 ④ (1) | 2024.10.22 |