문자열과 타입 안정성1
자바가 제공하는 열거형(Enum Type)을 제대로 이해하려면 먼저 열거형이 생겨난 이유를 알아야 한다.
예제를 순서대로 따라가며 열거형이 만들어진 근본적인 이유를 알아보자.
public class DiscountService {
public int discount(String grade, int price) {
int discountPercent = 0;
if (grade.equals("BASIC")) {
discountPercent = 10;
} 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 StringGradeEx0_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);
System.out.println("BASIC 등급의 할인 금액: " + basic);
System.out.println("GOLD 등급의 할인 금액: " + gold);
System.out.println("DIAMOND 등급의 할인 금액: " + diamond);
}
}
지금과 같이 단순히 문자열을 입력하는 방식은, 오타가 발생하기 쉽고, 유효하지 않는 값이 입력될 수 있다.
다음 예를 보자.
public class StringGradeEx0_2 {
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 diamondd = discountService.discount("DIAMONDD", price);
System.out.println("DIAMONDD 등급의 할인 금액: " + vip);
// 소문자 입력
int gold = discountService.discount("gold", price);
System.out.println("gold 등급의 할인 금액: " + gold);
}
}
실행 결과
VIP: 할인X
VIP 등급의 할인 금액: 0
DIAMONDD: 할인X
DIAMONDD 등급의 할인 금액: 0
gold: 할인X
gold 등급의 할인 금액: 0
예제에서는 다음과 같은 문제가 발생했다.
- 존재하지 않는 VIP라는 등급을 입력했다.
- 오타: DIAMOND 마지막에 D가 하나 추가되었다.
- 소문자 입력: 등급은 모두 대문자인데, 소문자를 입력했다.
등급에 문자열을 사용하는 지금의 방식은 다음과 같은 문제가 있다.
- 타입 안정성 부족: 문자열은 오타가 발생하기 쉽고, 유효하지 않은 값이 입력될 수 있다.
- 데이터 일관성: "GOLD", "gold", "Gold" 등 다양한 형식으로 문자열을 입력할 수 있어 일관성이 떨어진다.
String 사용 시 타입 안정성 부족 문제
- 값의 제한 부족: `String` 으로 상태나 카테고리를 표현하면, 잘못된 문자열을 실수로 입력할 가능성이 있다. 예를 들어, "Monday", "Tuesday" 등을 나타내는 데 `String` 을 사용한다면, 오타("Munday")나 잘못된 값 ("Funday")이 입력될 위험이 있다
- 컴파일 시 오류 감지 불가: 이러한 잘못된 값은 컴파일 시에는 감지되지 않고, 런타임에서만 문제가 발견되기 때문에 디버깅이 어려워질 수 있다.
이런 문제를 해결하려면 특정 범위로 값을 제한해야 한다.
예를 들어 BASIC, GOLD, DIAMOND라는 정확한 문자만 discount() 메서드에 전달되어야 한다.
하지만 String은 어떤 문자열이든 받을 수 있기 때문에 자바 문법 관점에서는 아무런 문제가 없다.
결국 String 타입을 사용해서는 문제를 해결할 수 없다.
문자열과 타입 안정성2
이번에는 대안으로 문자열 상수를 사용해보자.
상수는 미리 정의한 변수명을 사용할 수 있기 때문에 문자열을 직접 사용하는 것 보다는 더 안전하다.
public class StringGrade {
public static final String BASIC = "BASIC";
public static final String GOLD = "GOLD";
public static final String DIAMOND = "DIAMOND";
}
public class DiscountService {
//StringGrade를 참고하세요.
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;
}
}
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);
}
}
문자열 상수를 사용한 덕분에 전체적으로 코드가 더 명확해졌다.
그리고 discount()에 인자를 전달할 때도 StringGrade가 제공하는 문자열 상수를 사용하면 된다.
더 좋은 점은 만약 실수로 상수의 이름을 잘못 입력하면 컴파일 시점에 오류가 발생한다는 점이다.
따라서 오류를 쉽고 빠르게 찾을 수 있다. 하지만 문자열 상수를 사용해도, 지금까지 발생한 문제들을 근본적으로 해결할 수 는 없다. 왜냐하면 String 타입은 어떤 문자열이든 입력할 수 있기 때문이다. 어떤 개발자가 실수로 StringGrade에 있는 문자열 상수를 사용하지 않고, 다음과 같이 직접 문자열을 사용해도 막을 수 있는 방법이 없다.
타입 안전 열거형 패턴
타입 안전 열거형 패턴 - Type-Safe Enum Pattern
지금까지 설명한 문제를 해결하기 위해 많은 개발자들이 오랜기간 고민하고 나온 결과가 바로 타입 안전 열거형 패턴이다. 여기서 영어인 enum은 enumeration의 줄임말인데, 번역하면 열거라는 뜻이고, 어떤 항목을 나열하는 것을 뜻한다. 우리의 경우 회원 등급인 `BASIC` , `GOLD` , `DIAMOND` 를 나열하는 것이다.
여기서 중요한 것은 타입 안전 열거형 패 턴을 사용하면 이렇게 나열한 항목만 사용할 수 있다는 것이 핵심이다.
나열한 항목이 아닌 것은 사용할 수 없다. 쉽게 이야기해서 앞서본 `String` 처럼 아무런 문자열이나 다 사용할 수 있는 것이 아니라, 우리가 나열한 항목인 `BASIC` , `GOLD` , `DIAMOND` 만 안전하게 사용할 수 있다는 것이다.
타입 안전 열거형 패턴을 직접 구현해보자.
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade(); //x001
public static final ClassGrade GOLD = new ClassGrade(); //x002
public static final ClassGrade DIAMOND = new ClassGrade(); //x003
}
- 먼저 회원 등급을 다루는 클래스를 만들고, 각각의 회원 등급별로 상수를 선언한다.
- 이때 각각의 상수마다 별도의 인스턴스를 생성하고, 생성한 인스턴스를 대입한다.
- 각각을 상수로 선언하기 위해 static, final을 사용한다.
- static을 사용해서 상수를 메서드 영역에 선언한다.
- final을 사용해서 인스턴스(참조값)를 변경할 수 없게 한다.
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;
}
}
discount() 메서드는 매개변수로 ClassGrade 클래스를 사용한다.
값을 비교할 때는 `classGrade == ClassGrade.BASIC` 와 같이 `==` 참조값 비교를 사용하면 된다.
매개변수에 넘어오는 인수도 `ClassGrade` 가 가진 상수 중에 하나를 사용한다.
따라서 열거한 상수의 참조값으로 비교( `==` )하면 된다.
그런데 이 방식은 외부에서 임의로 `ClassGrade` 의 인스턴스를 생성할 수 있다는 문제가 있다.
이 문제를 해결하려면 외부에서 `ClassGrade` 를 생성할 수 없도록 막으면 된다. 기본 생성자를 `private` 으로 변경 하자.
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade(); //x001
public static final ClassGrade GOLD = new ClassGrade(); //x002
public static final ClassGrade DIAMOND = new ClassGrade(); //x003
//private 생성자 추가
private ClassGrade() {}
}
private 생성자를 사용해서 외부에서 `ClassGrade` 를 임의로 생성하지 못하게 막았다.
private 생성자 덕분에 ClassGrade의 인스턴스를 생성하는 것은 ClassGrade 클래스 내부에서만 할 수 있다.
앞서 우리가 정의한 상수들은 ClassGrade 클래스 내부에서 ClassGrade 객체를 생성한다.
이제 ClassGrade 인스턴스를 사용할 때는 ClassGrade 내부에 정의한 상수를 사용해야 한다.
그렇지 않으면 컴파일 오류가 발생한다. 쉽게 이야기해서 ClassGrade 타입에 값을 전달할 때는 우리가 앞서 열거한 BASIC, GOLD, DIAMOND 상수 만 사용할 수 있다.
이렇게 private 생성자까지 사용하면 타입 안전 열거형 패턴을 완성할 수 있다.
타입 안전 열거형 패턴 (Type-Safe Enum Pattern)의 장점
- 타입 안정성 향상: 정해진 객체만 사용할 수 있기 때문에, 잘못된 값을 입력하는 문제를 근본적으로 방지할 수 있 다.
- 데이터 일관성: 정해진 객체만 사용하므로 데이터의 일관성이 보장된다.
조금 더 자세히
- 제한된 인스턴스 생성: 클래스는 사전에 정의된 몇 개의 인스턴스만 생성하고, 외부에서는 이 인스턴스들만 사용 할 수 있도록 한다. 이를 통해 미리 정의된 값들만 사용하도록 보장한다.
- 타입 안전성: 이 패턴을 사용하면, 잘못된 값이 할당되거나 사용되는 것을 컴파일 시점에 방지할 수 있다. 예를 들어, 특정 메서드가 특정 열거형 타입의 값을 요구한다면, 오직 그 타입의 인스턴스만 전달할 수 있다. 여기서는 메서드의 매개변수로 `ClassGrade` 를 사용하는 경우, 앞서 열거한 `BASIC` , `GOLD` , `DIAMOND` 만 사용할 수 있다.
단점
이 패턴을 구현하려면 다음과 같이 많은 코드를 작성해야 한다.
그리고 `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 Type
자바는 타입 안전 열거형 패턴"(Type-Safe Enum Pattern)을 매우 편리하게 사용할 수 있는 열거형(Enum Type)을 제공한다. 쉽게 이야기해서 자바의 열거형은 앞서 배운 타입 안전 열거형 패턴을 쉽게 사용할 수 있도록 프로그래밍 언어에서 지원 하는 것이다.
영어인 enum은 enumeration의 줄임말인데, 번역하면 열거라는 뜻이고, 어떤 항목을 나열하는 것을 뜻한다.
"Enumeration"은 일련의 명명된 상수들의 집합을 정의하는 것을 의미하며, 프로그래밍에서는 이러한 상수들을 사용하여 코드 내에서 미리 정의된 값들의 집합을 나타낸다. 쉽게 이야기해서 회원의 등급은 상수로 정의한 BASIC, GOLD, DIAMOND만 사용할 수 있다는 뜻이다. 자바의 enum은 타입 안전성을 제공하고, 코드의 가독성을 높이며, 예상 가능한 값들의 집합을 표현하는 데 사용된다.
public enum Grade {
BASIC, GOLD, DIAMOND
}
- 열거형을 정의할 때는 `class` 대신에 enum을 사용한다.
- 원하는 상수의 이름을 나열하면 된다.
자바의 열거형으로 작성한 `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` 을 상속 받는다.
- 외부에서 임의로 생성할 수 없다.
열거형은 외부 생성 불가
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 > 중급1' 카테고리의 다른 글
8. 중첩 클래스, 내부 클래스2 (0) | 2024.09.01 |
---|---|
7. 중첩 클래스, 내부 클래스1 (0) | 2024.08.31 |
4. 래퍼, Class 클래스 (0) | 2024.08.27 |
3. String 클래스 (0) | 2024.08.27 |
2. 불변 객체 (0) | 2024.07.14 |