참조 - 김영한의 실전 자바 중급 1편
ENUM
자바는 타입 안전 열거형 패턴(Type-Safe Enum Pattern)을 매우 편리하게 사용할 수 있는 열거형(Enum Type)을 제공한다.
enum은 열거형 상수를 정의하는 특별한 클래스로 열거형을 사용하면 상수 값을 하나의 그룹으로 묶어 처리할 수 있어 코드의 가독성과 유지보수성을 높일 수 있다.
타입 안전 열거형 패턴(Type-Safe Enum Pattern)의 장단점
장점
- 타입 안정성 향상: 정해진 객체만 사용할 수 있기 때문에, 잘못된 값을 입력하는 문제를 근본적으로 방지할 수 있다.
- 데이터 일관성: 정해진 객체만 사용하므로 데이터의 일관성이 보장된다.
- 제한된 인스턴스 생성: 클래스는 사전에 정의도니 몇 개의 인스턴스만 생성하고, 외부에서는 이 인스턴스들만 사용할 수 있도록 한다. 이를 통해 미리 정의도니 값들만 사용하도록 보장한다.
- 타입 안정성: 이 패턴을 사용하면 잘못된 값이 할당되거나 사용되는 것을 컴파일 시점에서 방지할 수 있다.
단점
- 이 패턴을 구현하려면 다음과 같이 많은 코드를 작성해야 한다. 그리고 private 생성자를 추가하는 등 유의해야 되는 부분도 있다
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade();
public static final ClassGrade GOLD = new ClassGrade();
public static final ClassGrade DIAMOND = new ClassGrade();
//private 생성자 추가
private ClassGrade() {}
}
ENUM의 특징
고정된 상수 집합
enum은 고정된 상수 값의 집합을 정의할 때 사용한다, 예를 들어 요일, 방향, 상태와 같은 값들을 enum으로 정의할 수 있다.
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
타입 안정성
enum을 사용하면 컴파일 시 타입 체크가 가능하여 잘못된 값이 사용되는 것을 방지할 수 있다. 이는 일반적인 상수 정의 방식(static final)보다 안전하다.
Day today = Day.MONDAY;
메서드와 필드 추가 기능
enum은 메서드와 필드를 가질 수 있으며, 이는 더욱 복잡한 상수 정의를 가능하게 한다.
public enum Day {
SUNDAY("Sunday"),
MONDAY("Monday"),
TUESDAY("Tuesday"),
WEDNESDAY("Wednesday"),
THURSDAY("Thursday"),
FRIDAY("Friday"),
SATURDAY("Saturday");
private String displayName;
private Day(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
내부 클래스와 유사한 구조
enum은 내부적으로 클래스처럼 동작한다. 따라서 enum 내부에 생성자, 메서드, 필드 등을 정의할 수 있다.
ENUM의 장단점
장점
- 가독성: enum을 사용하면 코드의 가독성이 높아진다. 상수 집합을 명확하게 정의하고, 의미 있는 이름을 부여할 수 있다.
- 타입 안정성: enum을 사용하면 잘못된 값이 사용되는 것을 방지할 수 있어 코드의 안전성이 향상된다.
- 유지보수성: enum을 사용하면 상수 값을 추가하거나 수정할 때 코드 전반에 걸쳐 쉽게 변경할 수 있다.
- 메서드와 필드 추가 기능: enum에 메서드와 필드를 추가하여 복잡한 로직을 처리할 수 있다.
- 내부 동작: enum은 내부적으로 java.lang.Enum 클래스를 상속받아 동작한다. 따라서 enum 상수는 객체로 취급되며, 여러 가지 유용한 메서드를 사용할 수 있다.
단점
- 고정된 상수 집합: enum은 고정된 상수 집합을 정희하는 데 적합하지만, 런타임에 상수를 동적으로 추가하거나 변경할 수 없다.
- 상수 추가 시 재컴파일 필요: 새로운 상수를 추가하거나 기존 상수를 변경하려면 해당 enum을 사용하는 모든 코드를 다시 컴파일해야 한다.
- 복잡성 증가: enum에 너무 많은 메서드나 필드를 추가하면 오히려 복잡성이 증가할 수 있다.
ENUM 주요 메서드
- values() : 모든 ENUM 상수를 포함하는 배열을 반환한다.
- valueOf(String name) : 주어진 이름과 일치하는 ENUM 상수를 반환한다.
- name() : ENUM 상수의 이름을 문자열로 반환한다.
- ordinal() : ENUM 상수의 선언 순서(0부터 시작)를 반환한다.
- toString() : ENUM 상수의 이름을 문자열로 반환한다. name() 메서드와 유사하지만, toString()은 직접 오버라이드 할 수 있다.
※ 주의 : ordinal()은 가급적 사용하지 않는 것이 좋다.
- odinal()의 값은 가급적 사용하지 않는 것이 좋은데, 이 값을 사용하다가 중간에 상수를 선언하는 위치가 변경되면 전체 상수의 위치가 모두 변경될 수 있기 때문이다.
- 예를 들어 BASIC, GOLD, DIAMOND 라는 3개의 등급 중 BASIC과 GOLD 사이에 SILVER 등급이 추가되면 GOLD와 DIAMOND의 값이 하나씩 추가 된다
- 기존: BASIC(0), GOLD(1), DIAMOND(2)
- 추가: BASIC(0), SILVER(1), GOLD(2), DIAMOND(3)
- 위 같은 상황이 문제가 되는 경우는 DB에 GOLD의 ordinal() 값인 1을 저장 중에 SILVER가 추가되면 DB 값은 1로 유지되지만 애플리케이션 상에서는 GOLD는 2가 되고 SILVER는 1이기 때문에 버그가 발생하게 된다.
ENUM 예제 코드
예제: 회원 등급별 할인 코드
BASIC, GOLD, DIAMOND 3가지 회원 등급을 두고 각각 10, 20, 30% 할인되도록 하고 회원이 아닌 경우 할인이 없도록 하는 코드를 구현한다.
Enum을 사용하지 않은 코드
package enumeration.ex1;
public class StringGrade {
public static final String BASIC = "BASIC";
public static final String GOLD = "GOLD";
public static final String DIAMOND = "DIAMOND";
}
package enumeration.ex1;
public class DiscountService {
public int discount(String grade, int price) {
int discountPercent = 0;
if (grade.equals(StringGrade.BASIC)) {
discountPercent = 10;
} else if (grade.equals(StringGrade.GOLD)) {
discountPercent = 20;
} else if (grade.equals(StringGrade.DIAMOND)) {
discountPercent = 30;
} else {
System.out.println(grade + ": 할인X");
}
return price * discountPercent / 100;
}
}
package enumeration.ex1;
public class StringGradeEx1_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(StringGrade.BASIC, price);
int gold = discountService.discount(StringGrade.GOLD, price);
int diamond = discountService.discount(StringGrade.DIAMOND, price);
System.out.println("BASIC 등급의 할인 금액: " + basic);
System.out.println("GOLD 등급의 할인 금액: " + gold);
System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
}
}
- 문자열 상수를 사용해 실수로 상수의 이름을 잘못 입력해도 컴파일 시점에 오류를 찾을 수 있도록 함
- String 타입은 어떤 문자열이든 입력할 수 있기 때문에 개발자가 StringGrade에 있는 문자열 상수를 사용하지 않고 직접 문자열을 사용해도 막을 수 있는 방법이 없다.
Enum을 사용한 코드
package enumeration.ref3;
public enum Grade {
BASIC(10), GOLD(20), DIAMOND(30), VIP(40);
private final int discountPercent;
Grade(int discountPercent) {
this.discountPercent = discountPercent;
}
public int getDiscountPercent() {
return discountPercent;
}
public int discount(int price) {
return price * discountPercent / 100;
}
}
package enumeration.ref3;
public class EnumRefMain3_4 {
public static void main(String[] args) {
int price = 10000;
Grade[] grades = Grade.values();
for (Grade grade : grades) {
printDiscount(grade, price);
}
}
private static void printDiscount(Grade grade, int price) {
System.out.println(grade.name() + " 등급의 할인 금액: " + grade.discount(price));
}
}
- Enum 클래스를 사용하여 위에서 발생했던 문제를 방지할 수 있으며, 예시 코드처럼 코드를 간결하게 리팩토링 할 수 있다.
예제 : 인증 등급 만들기
문제 설명
- 회원의 인증 등급을 AuthGrade 라는 이름의 열거형으로 만들어라.
- 인증 등급은 다음 3가지이고, 인증 등급에 따른 레벨과 설명을 가진다. 레벨과 설명을 getXxx() 메서드로 조회할 수 있어야 한다.
- GUEST(손님)
- level = 1
- description = 손님
- LOGIN(로그인 회원)
- level=2
- description=로그인 회원
- ADMIN(관리자)
- level=3
- description=관리자
정답 코드
package enumeration.test.ex1;
public enum AuthGrade {
GUEST(1, "손님"),
LOGIN(2, "로그인 회원"),
ADMIN(3, "관리자");
private int level;
private String description;
AuthGrade(int level, String description) {
this.level = level;
this.description = description;
}
public int getLevel() {
return level;
}
public String getDescription() {
return description;
}
}
예제 2: 인증 등급 열거형 조회하기
문제 설명
- AuthGradeMain1 이라는 클래스를 만들고 다음 결과가 출력되도록 코드를 작성해라.
- AuthGrade 코드를 활용해라.
grade=GUEST, level=1, 설명=손님
grade=LOGIN, level=2, 설명=로그인 회원
grade=ADMIN, level=3, 설명=관리자
정답 코드
package enumeration.test.ex1;
public class AuthGradeMain1 {
public static void main(String[] args) {
AuthGrade[] authGrades = AuthGrade.values();
for (AuthGrade grade : authGrades) {
System.out.println("grade =" + grade.name() + ", level=" + grade.getLevel() + ", description=" + grade.getDescription());
}
}
}
예제 3: 인증 등급 열거형 활용하기
문제 설명
- AuthGradeMain2 클래스에 코드를 작성해라.
- 인증 등급을 입력 받아 AuthGrade 열거형으로 변환해라.
- 인증 등급에 따라 접근할 수 있는 화면이 다르다.
- 각각의 등급에 따라 출력되는 메뉴 목록이 달라진다(예: ADMIN 모든 화면 접근 가능, GUEST 메인화면만 접근 가능)
- 다음 출력 결과를 참고해서 코드를 완성해라.
GUEST
당신의 등급을 입력하세요[GUEST, LOGIN, ADMIN]: GUEST
당신의 등급은 손님입니다.
==메뉴 목록==
- 메인 화면
LOGIN
당신의 등급을 입력하세요[GUEST, LOGIN, ADMIN]: LOGIN
당신의 등급은 로그인 회원입니다.
==메뉴 목록==
- 메인 화면
- 이메일 관리 화면
ADMIN
당신의 등급을 입력하세요[GUEST, LOGIN, ADMIN]: ADMIN
당신의 등급은 관리자입니다.
==메뉴 목록==
- 메인 화면
- 이메일 관리 화면
- 관리자 화면
정답 코드
package enumeration.test.ex1;
import java.util.Scanner;
public class AuthGradeMain2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("당신의 등급을 입력하세요[GUEST, LOGIN, ADMIN]: ");
String grade = sc.nextLine();
AuthGrade authGrade = AuthGrade.valueOf(grade.toUpperCase());
System.out.println("당신의 등급은 " + authGrade.getDescription() + "입니다.");
System.out.print("==메뉴 목록==");
if (authGrade.getLevel() > 0) {
System.out.println("- 메인 화면");
}
if (authGrade.getLevel() > 1) {
System.out.println("- 이메일 관리 화면");
}
if (authGrade.getLevel() > 2) {
System.out.println("- 관리자 화면");
}
}
}
예제 4: HTTP 상태 코드
문제 설명
- HttpStatus 열거형을 만들어라.
- HTTP 상태 코드 정의
- OK
- code: 200
- message: "OK"
- BAD_REQUEST
- code: 400
- message: "Bad Request"
- NOT_FOUND
- code: 404
- message: "Not Found"
- INTERNAL_SERVER_ERROR
- code: 500
- message: "Internal Server Error"
- OK
아래의 코드를 참조해서 HttpStatus 열거형을 완성해라.
package enumeration.test.http;
import java.util.Scanner;
public class HttpStatusMain {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("HTTP CODE: ");
int httpCodeInput = sc.nextInt();
HttpStatus status = HttpStatus.findByCode(httpCodeInput);
if (status == null) {
System.out.println("정의되지 않은 코드");
} else {
System.out.println(status.getCode() + " " + status.getMessage());
System.out.println("isSuccess = " + status.isSuccess());
}
}
}
정답 코드
package enumeration.test.http;
public enum HttpStatus {
OK(200, "OK"),
BAD_REQUEST(400, "Bad Request"),
NOT_FOUND(404, "Not Found"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public static HttpStatus findByCode(int code) {
for (HttpStatus status : values()) {
if (status.getCode() == code) {
return status;
}
}
return null;
}
public boolean isSuccess() {
return code >= 200 && code < 299;
}
}
'항해 99 > Java' 카테고리의 다른 글
Java - 정렬 알고리즘 (0) | 2024.08.08 |
---|---|
Java - 날짜와 시간 (0) | 2024.07.26 |
Java - Wrapper, Class, System, Math, Random (0) | 2024.06.13 |
String 클래스 (0) | 2024.06.05 |
Java - 불변 객체 (0) | 2024.05.31 |