Java 공부 45일차(열거형 - ENUM이란?)2편

임선구·2025년 3월 27일

몸 비틀며 Java

목록 보기
46/58

오늘의 코딩


오늘의 공부 이따간 또 합주하러 가야한다. 합주 하기 전 짬내서 코딩


열거형 - Enum Type

자바는 타입 안전 열거형 패턴"(Type-Safe Enum Pattern)을 매우 편리하게 사용할 수 있는 열거형(Enum Type)을
제공한다.

쉽게 이야기해서 자바의 열거형은 앞서 배운 타입 안전 열거형 패턴을 쉽게 사용할 수 있도록 프로그래밍 언어에서 지원
하는 것이다.

영어인 enumenumeration 의 줄임말인데, 번역하면 열거라는 뜻이고, 어떤 항목을 나열하는 것을 뜻한다.
"Enumeration"은 일련의 명명된 상수들의 집합을 정의하는 것을 의미하며, 프로그래밍에서는 이러한 상수들을 사용하
여 코드 내에서 미리 정의된 값들의 집합을 나타낸다.
쉽게 이야기해서 회원의 등급은 상수로 정의한 BASIC , GOLD , DIAMOND 만 사용할 수 있다는 뜻이다.
자바의 enum은 타입 안전성을 제공하고, 코드의 가독성을 높이며, 예상 가능한 값들의 집합을 표현하는 데 사용된다.

package enumeration.ex3;
public enum Grade {
 BASIC, GOLD, DIAMOND
}
  • 열거형을 정의할 때는 class 대신에 enum 을 사용한다.
  • 원하는 상수의 이름을 나열하면 된다.

앞서 직접 ClassGrade 를 구현할 때와는 비교가 되지 않을 정도로 편리하다.
자바의 열거형으로 작성한 Grade 는 다음 코드와 거의 같다.

public class Grade extends Enum {
 public static final Grade BASIC = new Grade();
 public static final Grade GOLD = new Grade();
 public static final Grade DIAMOND = new Grade();
 //private 생성자 추가
 private Grade() {}
}
  • 열거형도 클래스이다.
  • 열거형은 자동으로 java.lang.Enum 을 상속 받는다.
  • 외부에서 임의로 생성할 수 없다.


열거형을 코드로 확인해보자.

package enumeration.ex3;
public class EnumRefMain {
 public static void main(String[] args) {
 System.out.println("class BASIC = " + Grade.BASIC.getClass());
 System.out.println("class GOLD = " + Grade.GOLD.getClass());
 System.out.println("class DIAMOND = " + Grade.DIAMOND.getClass());
 System.out.println("ref BASIC = " + refValue(Grade.BASIC));
 System.out.println("ref GOLD = " + refValue(Grade.GOLD));
 System.out.println("ref DIAMOND = " + refValue(Grade.DIAMOND));
 }
 private static String refValue(Object grade) {
 return Integer.toHexString(System.identityHashCode(grade));
 }
}

실행 결과

class BASIC = class enumeration.ex3.Grade
class GOLD = class enumeration.ex3.Grade
class DIAMOND = class enumeration.ex3.Grade
ref BASIC = x001
ref GOLD = x002
ref DIAMOND = x003
  • 실행 결과를 보면 상수들이 열거형으로 선언한 타입인 Grade 타입을 사용하는 것을 확인할 수 있다. 그리고 각각의 인스턴스도 서로 다른 것을 확인할 수 있다.
  • 참고로 열거형은 toString() 을 재정의 하기 때문에 참조값을 직접 확인할 수 없다. 참조값을 구하기 위해
    refValue() 를 만들었다.
    • System.identityHashCode(grade) : 자바가 관리하는 객체의 참조값을 숫자로 반환한다.
    • Integer.toHexString() : 숫자를 16진수로 변환, 우리가 일반적으로 확인하는 참조값은 16진수
  • 열거형도 클래스이다. 열거형을 제공하기 위해 제약이 추가된 클래스라 생각하면 된다.

자바의 열거형을 사용해서 코드를 작성해보자.

package enumeration.ex3;
public class DiscountService {
 public int discount(Grade grade, int price) {
 int discountPercent = 0;
 //enum switch 변경 가능
 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;
 }}
package enumeration.ex3;
public class EnumEx3_1 {
 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);
 }
}

실행 결과

BASIC 등급의 할인 금액: 1000
GOLD 등급의 할인 금액: 2000
DIAMOND 등급의 할인 금액: 3000
  • 열거형의 사용법이 앞서 타입 안전 열거형 패턴을 직접 구현한 코드와 같은 것을 확인 할 수 있다.
  • 참고로 열거형은 switch 문에 사용할 수 있는 장점도 있다.

열거형은 외부 생성 불가

package enumeration.ex3;
public class EnumEx3_2 {
 public static void main(String[] args) {
 int price = 10000;
 DiscountService discountService = new DiscountService();
/* Grade myGrade = new Grade(); //enum 생성 불가
 double result = discountService.discount(myGrade, price);
 System.out.println("result price: " + result);
*/
 }
}
  • enum 은 열거형 내부에서 상수로 지정하는 것 외에 직접 생성이 불가능하다. 생성할 경우 컴파일 오류가 발생한다.
    • 오류 메시지: enum classes may not be instantiated

열거형(ENUM)의 장점

  • 타입 안정성 향상: 열거형은 사전에 정의된 상수들로만 구성되므로, 유효하지 않은 값이 입력될 가능성이 없다. 이런 경우 컴파일 오류가 발생한다.
  • 간결성 및 일관성: 열거형을 사용하면 코드가 더 간결하고 명확해지며, 데이터의 일관성이 보장된다.
  • 확장성: 새로운 회원 등급을 타입을 추가하고 싶을 때, ENUM에 새로운 상수를 추가하기만 하면 된다.

참고
열거형을 사용하는 경우 static import 를 적절하게 사용하면 더 읽기 좋은 코드를 만들 수 있다.

열거형 - 주요 메서드

모든 열거형은 java.lang.Enum 클래스를 자동으로 상속 받는다. 따라서 해당 클래스가 제공하는 기능들을 사용할 수 있다.

package enumeration.ex3;
import java.util.Arrays;
public class EnumMethodMain {
 public static void main(String[] args) {
 //모든 ENUM 반환
 Grade[] values = Grade.values(); System.out.println("values = " + Arrays.toString(values));
 for (Grade value : values) {
 System.out.println("name=" + value.name() + ", ordinal=" +
value.ordinal());
 }
 //String -> ENUM 변환, 잘못된 문자면 IllegalArgumentException 발생
 String input = "GOLD";
 Grade gold = Grade.valueOf(input);
 System.out.println("gold = " + gold); //toString() 오버라이딩 가능
 }
}

실행 결과

values = [BASIC, GOLD, DIAMOND]
name=BASIC, ordinal=0
name=GOLD, ordinal=1
name=DIAMOND, ordinal=2
gold = GOLD

Arrays.toString() 배열의 참조값이 아니라 배열 내부의 값을 출력할 때 사용한다.

ENUM - 주요 메서드

  • values(): 모든 ENUM 상수를 포함하는 배열을 반환한다.
  • valueOf(String name): 주어진 이름과 일치하는 ENUM 상수를 반환한다.
  • name(): ENUM 상수의 이름을 문자열로 반환한다.
  • ordinal(): ENUM 상수의 선언 순서(0부터 시작)를 반환한다.
  • toString(): ENUM 상수의 이름을 문자열로 반환한다. name() 메서드와 유사하지만, toString() 은 직접 오버라이드 할 수 있다.

주의 ordinal()은 가급적 사용하지 않는 것이 좋다.

  • ordinal() 의 값은 가급적 사용하지 않는 것이 좋다. 왜냐하면 이 값을 사용하다가 중간에 상수를 선언하는 위
    치가 변경되면 전체 상수의 위치가 모두 변경될 수 있기 때문이다.
  • 예를 들어 중간에 BASIC 다음에 SILVER 등급이 추가되는 경우 GOLD , DIAMOND 의 값이 하나씩 추가된다.

기존

  • BASIC: 0
  • GOLD: 1
  • DIAMOND: 2
    추가
  • BASIC: 0
  • SILVER: 1
  • GOLD: 2
  • DIAMOND: 3

기존 GOLDordinal() 값인 1 을 데이터베이스나 파일에 저장하고 있었는데, 중간에 SILVER 가 추가되면 데이터베이스나 파일에 있는 값은 그대로 1로 유지되지만, 애플리케이션 상에서 GOLD 는 2가 되고, SILVER 는 1이 된다.
쉽게 이야기해서 ordinal() 의 값을 사용하면 기존 GOLD 회원이 갑자기 SILVER 가 되는 큰 버그가 발생할 수 있다.

열거형 정리

  • 열거형은 java.lang.Enum 를 자동(강제)으로 상속 받는다.
  • 열거형은 이미 java.lang.Enum 을 상속 받았기 때문에 추가로 다른 클래스를 상속을 받을 수 없다.
  • 열거형은 인터페이스를 구현할 수 있다.
  • 열거형에 추상 메서드를 선언하고, 구현할 수 있다.
    • 이 경우 익명 클래스와 같은 방식을 사용한다. 익명 클래스는 뒤에서 다룬다.

열거형 - 리팩토링1

이번 시간에는 지금까지 구현한 코드들을 더 읽기 쉽게 리팩토링해보자.
아직 열거형(ENUM)에 익숙하지 않으니, 앞서 클래스를 직접 사용해서 열거형 패턴을 구현했던 ex2 의 코드를 먼저
리팩토링해보자.

DiscountService.discount() 코드를 살펴보자.

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");
}
  • 불필요한 if 문을 제거하자.
  • 이 코드에서 할인율( discountPercent )은 각각의 회원 등급별로 판단된다. 할인율은 결국 회원 등급을 따라
    간다. 따라서 회원 등급 클래스가 할인율( discountPercent )을 가지고 관리하도록 변경하자.
package enumeration.ref1;
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 에 할인율( discountPercent ) 필드를 추가했다. 조회 메서드도 추가한다.
  • 생성자를 통해서만 discountPercent 를 설정하도록 했고, 중간에 이 값이 변하지 않도록 불변으로 설계했다.
  • 정리하면 상수를 정의할 때 각각의 등급에 따른 할인율( discountPercent )이 정해진다.
package enumeration.ref1;
public class DiscountService {
 public int discount(ClassGrade classGrade, int price) {
 return price * classGrade.getDiscountPercent() / 100;
 }
}
  • 기존에 있던 if 문이 완전히 제거되고, 단순한 할인율 계산 로직만 남았다.
  • 기존에는 if 문을 통해서 회원의 등급을 찾고, 각 등급 별로 discountPercent 의 값을 지정했다.
  • 변경된 코드에서는 if 문을 사용할 이유가 없다. 단순히 회원 등급안에 있는 getDiscountPercent() 메서드를 호출하면 인수로 넘어온 회원 등급의 할인율을 바로 구할 수 있다.
package enumeration.ref1;
public class ClassGradeRefMain1 {
 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);
 }
}

실행 결과

BASIC 등급의 할인 금액: 1000
GOLD 등급의 할인 금액: 2000
DIAMOND 등급의 할인 금액: 3000

실행 결과는 기존 코드와 같다.

profile
끝까지 가면 내가 다 이겨

0개의 댓글