BASIC
, GOLD
, DIAMOND
의 3등급으로 나누고, 상품 구매시 등급별로 할인을 적용한다. 이 때, 할인시 소수점 이하는 버린다.BASIC
: 10% 할인GOLD
: 20% 할인DIAMOND
: 30% 할인public class DiscountServiceV0 {
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_2 {
public static void main(String[] args) {
int price = 10000;
DiscountServiceV0 discountService = new DiscountServiceV0();
// 존재하지 않는 등급
int vip = discountService.discount("VIP", price);
System.out.println("VIP 등급의 할인 금액: " + vip);
// 오타
int diamondd = discountService.discount("DIAMONDD", price);
System.out.println("DIAMONDD 등급의 할인 금액: " + diamondd);
// 소문자 입력
int gold = discountService.discount("gold", price);
System.out.println("gold 등급의 할인 금액: " + gold);
}
}
String
으로 상태나 카테고리를 표현하면 잘못된 문자열을 실수로 입력할 가능성이 있다.public class StringGrade {
public static final String BASIC = "BASIC";
public static final String GOLD = "GOLD";
public static final String DIAMOND = "DIAMOND";
}
public class DiscountServiceV1 {
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;
DiscountServiceV1 discountService = new DiscountServiceV1();
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
에 있는 문자열 상수를 사용하지 않고, 다음과 같이 직접 문자열을 사용해도 막을 수 있는 방법이 없다.public class ClassGrade {
private static final ClassGrade BASIC = new ClassGrade();
private static final ClassGrade GOLD = new ClassGrade();
private static final ClassGrade DIAMOND = new ClassGrade();
}
public class ClassRefMain {
public static void main(String[] args) {
System.out.println("class BASIC = " + ClassGrade.BASIC.getClass());
System.out.println("class GOLD = " + ClassGrade.GOLD.getClass());
System.out.println("class DIAMOND = " + ClassGrade.DIAMOND.getClass());
System.out.println("ref BASIC = " + ClassGrade.BASIC);
System.out.println("ref GOLD = " + ClassGrade.GOLD);
System.out.println("ref DIAMOND = " + ClassGrade.DIAMOND);
}
}
각각의 상수는 모두 ClassGrade
타입을 기반으로 인스턴스를 만들었기 때문에 getClass()
의 결과는 모두 같다.
각각의 상수는 모두 서로 다른 ClassGrade
인스턴스를 참조하기 때문에 참조값이 다르게 출력된다.
static final
이므로 상수이며 static
이므로 애플리케이션 로딩 시점에 3개의 ClassGrade
인스턴스가 생성되고, 각각의 상수는 같은 ClassGrade
타입의 서로 다른 인스턴스 참조값을 가진다.
public class DiscountServiceV2 {
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 ClassGradeEx2_1 {
public static void main(String[] args) {
int price = 10000;
DiscountServiceV2 discountService = new DiscountServiceV2();
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);
}
}
discount()
메서드는 매개변수로 ClassGrade
클래스를 사용한다.classGrade == ClassGrade.BASIC
와 같이 ==
참조값 비교를 사용한다.public class ClassGradeEx2_2 {
public static void main(String[] args) {
int price = 10000;
DiscountServiceV2 discountService = new DiscountServiceV2();
// ClassGrade newClassGrade = new ClassGrade(); // 외부에서 임의로 인스턴스 생성할 수 있다는 문제가 발생
// int result = discountService.discount(newClassGrade, price);
// System.out.println("newClassGrade 등급의 할인 금액: " + result);
}
}
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() {
}
}
ClassGrade
인스턴스를 생성할 수 있다는 문제점이 있다.private
으로 제한하면 된다.private
생성자를 추가하는 등 유의해야 하는 부분들도 존재한다.자바는 타입 안전 열거형 패턴을 매우 편리하게 사용할 수 있는 열거형을 제공한다.
Enumeration
은 일련의 명명된 상수들의 집합을 정의하는 것을 의미하며, 프로그래밍에서는 이러한 상수들을 사용하여 코드 내에서 미리 정의된 값들의 집합을 나타낸다.
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() {}
}
public class EnumMethodMain {
public static void main(String[] args) {
Grade[] grades = Grade.values();
for (Grade grade : grades) {
System.out.println("grade = " + grade);
}
String input = "GOLD";
Grade grade = Grade.valueOf(input);
System.out.println("grade = " + grade);
System.out.println(Grade.BASIC.ordinal());
System.out.println(Grade.GOLD.ordinal());
System.out.println(Grade.DIAMOND.ordinal());
}
}
values()
: 모든 ENUM 상수를 포함하는 배열을 반환한다.valueOf(String name)
: 주어진 이름과 일치하는 ENUM 상수를 반환한다.name()
: ENUM 상수의 이름을 문자열로 반환한다.ordinal()
: ENUM 상수의 선언 순서(0부터 시작)를 반환한다.toString()
: ENUM 상수의 이름을 문자열로 반환한다. name()
메서드와 유사하지만, toString()
은 직접 오버라이드 할 수 있다BASIC(0)
, GOLD(1)
, DIAMOND(2)
인데 만약 값이 1인 위치에 SILVER
가 추가되면 데이터베이스나 파일에 있는 값은 그대로 1로 유지가 되지만 애플리케이션에서 갑자기 GOLD(2)
로 변경이 되고 SILVER(1)
가 된다. 이렇게 되면 기존 데이터와의 일관성이 보장되지 않아 큰 버그가 발생할 수 있다.public enum Grade {
BASIC(10),
GOLD(20),
DIAMOND(30);
private final int discountPercent;
Grade(int discountPercent) {
this.discountPercent = discountPercent;
}
public int discount(int price) {
return price * discountPercent / 100;
}
}
public class EnumRefMain1 {
public static void main(String[] args) {
int price = 10000;
printDiscount(Grade.BASIC, price);
printDiscount(Grade.GOLD, price);
printDiscount(Grade.DIAMOND, price);
}
private static void printDiscount(Grade grade, int price) {
System.out.println(grade.name() + " 등급의 할인 금액: " + grade.discount(price));
}
}
public class EnumRefMain2 {
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));
}
}
Grade
내부에서 discount()
메서드를 만들어 할인율을 스스로 계산한다.Grade
에 새로운 등급이 추가되더라도 코드 변경없이 모든 등급의 할인을 출력할 수 있다.