자바가 제공하는 열거형(Enum Type)을 제대로 이해하려면 먼저 열거형이 생겨난 이유를 알아야 한다.
public class DiscountService {
public int discount(String grade, int price){
int discountPercent = 0;
if (grade.equals("BASIC")){
discountPercent = 0;
} else if (grade.equals("GOLD")){
discountPercent = 20;
} else if (grade.equals("DIAMOND")){
discountPercent = 30;
} else {
System.out.println(grade + " 할인 X");
}
return price * discountPercent / 100;
}
}
public class StringGradeEx_0 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount("BASIC", price);
int gold = discountService.discount("GOLD", price);
int diamond = discountService.discount("DIAMOND", price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
public class StringGradeEx_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount("BASIC", price);
int gold = discountService.discount("GOLD", price);
int diamond = discountService.discount("DIAMOND", price);
//존재하지 않는 등급
int vip = discountService.discount("VIP", price);
//오타
int diamonnd = discountService.discount("DIAMONDD", price);
//대소문자
int gold1 = discountService.discount("Gold" , price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
문제점
등급에 문자열을 사용하는 지금의 방식은 문제가 있음
String 사용시 타입 안정성 문제
값의 제한 부족 : String으로 상태나 카테고리를 표현하면 잘못된 문자열을 실수로 입력할 가능성
컴파일 시 오류 감지 불가 : 잘못된 값에는 컴파일 시에는 감지 X. 런타임시 문제 발견
public class StringGrade {
public static final String BASIC = "BASIC";
public static final String GOLD = "BASIC";
public static final String DIAMOND = "DIAMOND";
}
public class DiscountService {
public int discount(String grade, int price){
int discountPercent = 0;
if (grade.equals(StringGrade.BASIC)){
discountPercent = 0;
} 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;
}
}
public class StringGradeEx_0 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(BASIC, price);
int gold = discountService.discount(GOLD, price);
int diamond = discountService.discount(DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
문자열 상수를 사용한 덕분에 전체적으로 코드 명확, 인자를 전달할 때도 StringGrade가 제공하는 문자열 상수를 사용, 더 좋은 점은 만약 실수로 상수의 이름을 잘못 입력하면 컴파일 시점에 오류 발생
하지만 문자열 상수를 사용해도 지금까지 발생한 문제점들을 극복 X, String 타입은 어떤 문자열이든 입력 가능.
public class StringGradeEx_1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
//존재하지 않는 등급
int vip = discountService.discount("VIP", price);
System.out.println("vip = " + vip);
//오타
int diamonnd = discountService.discount("DIAMONDD", price);
System.out.println("diamonnd = " + diamonnd);
//대소문자
int gold1 = discountService.discount("Gold" , price);
System.out.println("gold1 = " + gold1);
}
}
그리고 사용해야 하는 문자열 상수가 어디 있는지 discount() 호출하는 개발자가 알 수 있을까??
public int discount(String grade, int price) {}
주석을 남겨서 상수를 사용해달라고 해야 함
enum은 enumeration의 줄임말, -> 번역하면 열거라는 의미. 어떤 항목을 나열하는 것을 뜻함.
BASIC, GOLD, DIAMOND를 나열. 여기서 중요한 것은 타입 안전 열거형 패턴을 사용하면 이렇게 나열한 항목만 사용할 수 있는 것이 핵심.
나열하지 않은 항목은 사용 X
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade();
public static final ClassGrade GOLD = new ClassGrade();
public static final ClassGrade DIAMOND = new ClassGrade();

public class ClassRefMain {
public static void main(String[] args) {
System.out.println("BASIC = " + ClassGrade.BASIC.getClass());
System.out.println("GOLD = " + ClassGrade.GOLD.getClass());
System.out.println("DIAMOND = " + ClassGrade.DIAMOND.getClass());
System.out.println("BASIC = " + ClassGrade.BASIC);
System.out.println("GOLD = " + ClassGrade.GOLD);
System.out.println("DIAMOND = " + ClassGrade.DIAMOND);
}
}
각 상수는 ClassGrade 타입을 기반으로 인스턴스를 만들었기 때문에 getClass의 결과는 모두 classGrade
각 상수는 모두 서로 각각 다른 ClassGrade 인스턴스를 참조 -> 참조값 다르게 출역
public class DiscountService {
public int discount(ClassGrade classGrade, int price){
int discountPercent = 0;
if (classGrade == classGrade.BASIC){
discountPercent = 10;
} else if (classGrade == classGrade.GOLD){
discountPercent = 20;
} else if (classGrade == classGrade.DIAMOND){
discountPercent = 30;
} else {
System.out.println("할인 X");
}
return price * discountPercent / 100;
}
}
public class ClassGradeEx1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(ClassGrade.BASIC, price);
int gold = discountService.discount(ClassGrade.GOLD, price);
int diamond = discountService.discount(ClassGrade.DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
public class ClassGradeEx2 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
//private으로 생성 X 제한
//ClassGrade classGrade = new ClassGrade();
//int rst = discountService.discount(classGrade, price);
// System.out.println("rst = " + rst);
}
}
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(){}
}
private 생성자를 사용해 -> ClassGrade를 임의로 생성 X
private 생성자 덕분에 ClassGrade 인스턴스를 생성하는 것은 ClassGrade 클래스 내부에서만 할 수 있음. 앞서 우리가 정의한 상수들은 ClassGrade 클래스 내부에서 ClassGrade 객체를 생성
ClassGrade 인스턴스를 사용할 때는 ClassGrade 내부에 정의한 상수를 사용해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
타입 안전 열거형 패턴
타입 안정성 향상 -> 정해진 객체만 사용할 수 있기 때문에 잘못된 값을 입력하는 문제를 근본적 방지
데이터 일관성 : 정해진 객체만 사용, 데이터 일관성이 보장
제한된 인스턴스 : 클래스는 사전에 정의된 몇개의 인스턴스만 생성, 외부에서는 이 인스턴스들만 사용할 수 있도록 함
타입 안정성 : 이 패턴을 사용, 잘못된 값이 할당되거나 사용되는 것을 컴파일 시점에 방지 가능
public enum Grade {
BASIC, GOLD, DIAMOND
}

public class EnumMain {
public static void main(String[] args) {
System.out.println("BASIC = " + Grade.BASIC.getClass() );
System.out.println("GOLD = " + Grade.GOLD.getClass() );
System.out.println("DIAMOND = " + Grade.DIAMOND.getClass());
System.out.println("BASIC = " + refVal(Grade.BASIC));
System.out.println("BASIC = " + refVal(Grade.GOLD));
System.out.println("BASIC = " + refVal(Grade.DIAMOND));
}
public static String refVal(Grade grade){
return Integer.toHexString(System.identityHashCode(grade));
}
}
실행된 결과를 보면 상수들이 열거형으로 선언한 타입인 Grade 타입을 사용하는 것을 확인
각각의 인스턴스들도 서로 다른 것을 확인
참고로 열거형은 toString() 을 재정의 하기 때문에, 참조값을 직접 확인 X -> 참조값 구하기 위해ㅑ refValue를 만들었음
System.identityHashCode(grade): 자바가 관리하는 객체의 참조값을 숫자로 반환한다.
Integer.toHexString(): 숫자를 16진수로 변환, 우리가 일반적으로 확인하는 참조값은 16진수
public class DiscountService {
public int discount(Grade grade, int price){
int discountPercent = 0;
if (grade == Grade.BASIC){
discountPercent = 10;
} else if (grade == Grade.GOLD){
discountPercent = 20;
} else if (grade == Grade.DIAMOND){
discountPercent = 30;
} else {
System.out.println("할인 X");
}
return price * discountPercent / 100;
}
}
public class ClassGradeEx1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(Grade.BASIC, price);
int gold = discountService.discount(Grade.GOLD, price);
int diamond = discountService.discount(Grade.DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
열거형은 외부 생성 불가
public class ClassGradeEx2 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
//private으로 생성 X 제한
//ClassGrade classGrade = new ClassGrade();
//int rst = discountService.discount(classGrade, price);
// System.out.println("rst = " + rst);
}
}
타입 안정성 향상: 열거형은 사전에 정의된 상수들로만 구성되므로, 유효하지 않은 값이 입력될 가능성이 없다. 이런 경우 컴파일 오류가 발생한다.
간결성 및 일관성: 열거형을 사용하면 코드가 더 간결하고 명확해지며, 데이터의 일관성이 보장된다.
확장성: 새로운 회원 등급을 타입을 추가하고 싶을 때, ENUM에 새로운 상수를 추가하기만 하면 된다
public class EnumMethodMain {
public static void main(String[] args) {
//모든 Enum 변환
Grade[] grades = Grade.values();
System.out.println("grades = " + Arrays.toString(grades));
for (Grade grade : grades) {
System.out.println("name= " + grade.name() + " ordinal= " + grade.ordinal());
}
//String -> Enum 변환
String input = "GOLD";
Grade grade = Grade.valueOf(input);
System.out.println("grade = " + grade);
}
}
Arrays.toString 배열의 참조값이 아니라 배열의 내부 값을 출력할때 사용
ENUM - 주요 메서드
values() : 모든 ENUM 상수를 포함하는 배열을 반환한다.
valueOf(String name): 주어진 이름과 일치하는 ENUM 상수를 반환한다.
name(): ENUM 상수의 이름을 문자열로 반환한다.
ordinal(): ENUM 상수의 선언 순서(0부터 시작)를 반환한다.
toString(): ENUM 상수의 이름을 문자열로 반환한다. name() 메서드와 유사하지만, toString()은 직접 오버라이드 할 수 있다.
oridianl은 가급적 사용 X
이 값을 사용하다가 중간에 상수 위치 변경시 전체 상수의 위치가 변경
열거형 정리
열거형은 java.lang.Enum을 강제로 상속
열거형은 이미 java.lang.Enum을 상속 받았기 때문에 추가로 다른 클래스를 상속 받을 수 없다
열거형은 인터페이스를 구현
열거형에 추상 메서드를 선언, 구현
불필요한 if문 제거. disocuntPercent는 각각 회원 등급별로 판단. 할인율은 결구 회원 등급에 따라 간다.
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade(10);
public static final ClassGrade GOLD = new ClassGrade(20);
public static final ClassGrade DIAMOND = new ClassGrade(30);
private final int discountPercent;
private ClassGrade(int discountPercent) {
this.discountPercent = discountPercent;
}
public int getDiscountPercent() {
return discountPercent;
}
}
ClassGrade에 할인률 disocuntPercent만 필드 추가. 조회 메서드도 추가
생성자를 통해서만 disocuntPercent를 설정. 값이 변하지 않도록 불변으로 설계
public class DiscountService {
public int discount(ClassGrade classGrade, int price) {
return price * classGrade.getDiscountPercent() / 100;
}
}
기존에 있던 if문이 완전히 제거, 단순한 할인율 계산 로직만 남았다.
기존 if문을 통해서 회원의 등급을 찾고, 각 등급별로 discountPercent의 값 지정
if문 사용 이유 X
public class ClassGradeRefEx1 {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(ClassGrade.BASIC, price);
int gold = discountService.discount(ClassGrade.GOLD,price);
int diamond = discountService.discount(ClassGrade.DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
public enum Grade {
BASIC(10), GOLD(20), DIAMOND(30);
private final int discountPercent;
Grade(int discountPercent){
this.discountPercent = discountPercent;
}
public int getDiscountPercent(){
return discountPercent;
}
}
discountPercnet 필드를 추가, 생성자를 통해서 필드에 값 지정
열거형은 상수로 지정하는 것 외 일반적인 방법으로 생성 불가능 -> 접근제어자를 선언할 수 X
public class DiscountService {
public int discount(Grade grade, int price){
return price * grade.getDiscountPercent() / 100;
}
}
public class EnumRefMain {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(Grade.BASIC, price);
int gold = discountService.discount(Grade.GOLD, price);
int diamond = discountService.discount(Grade.DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
Grade 클래스 안으로 discount 메서드 이동
public enum Grade {
BASIC(10), GOLD(20), DIAMOND(30);
private final int discountPercent;
Grade(int discountPercent){
this.discountPercent = discountPercent;
}
public int getDiscountPercent(){
return discountPercent;
}
public int discount(int price){
return price * discountPercent / 100;
}
}
public class DiscountService {
public int discount(Grade grade, int price){
return grade.discount(price);
}
}
할인율 계산은 이제 Grade가 스스로 처리, 따라서 DiscountService.discount 메서드는 단순히 Grade.disocunt를 호출
public class EnumRefMain {
public static void main(String[] args) {
int price = 10000;
DiscountService discountService = new DiscountService();
int basic = discountService.discount(Grade.BASIC, price);
int gold = discountService.discount(Grade.GOLD, price);
int diamond = discountService.discount(Grade.DIAMOND, price);
System.out.println("basic = " + basic);
System.out.println("gold = " + gold);
System.out.println("diamond = " + diamond);
}
}
public class EnumRefMain_1 {
public static void main(String[] args) {
int price = 10000;
System.out.println("BASIC = " + Grade.BASIC.discount(price));
System.out.println("GOLD = " + Grade.GOLD.discount(price));
System.out.println("DIAMOND = " + Grade.DIAMOND.discount(price));
}
public static void printDiscount(Grade grade, int price){
System.out.println(grade.name() + " " + grade.discount(price));
}
}
DiscountService 더이상 필요 X
중복 부분 또한 제거
public class EnumRefMain2 {
public static void main(String[] args) {
int price = 10000;
Grade[] grades = Grade.values();
for (Grade grade : grades) {
printDiscount(grade, price);
}
}
public static void printDiscount(Grade grade, int price){
System.out.println(grade.name() + " " + grade.discount(price));
}
}