
package me.whiteship.chapter06.item34.integer_values;
public class Fruits {
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 3;
public static final int ORANGE_TEMPLE = 4;
public static final int ORANGE_BLOOD = 5;
}
package me.whiteship.chapter06.item34.integer_values;
public class FruitStore {
public int getPrice(int fruit) {
return switch (fruit) {
case Fruits.APPLE_FUJI -> 100;
case Fruits.APPLE_PIPPIN -> 200;
case Fruits.APPLE_GRANNY_SMITH -> 300;
case Fruits.ORANGE_NAVEL -> 150;
case Fruits.ORANGE_TEMPLE -> 250;
case Fruits.ORANGE_BLOOD -> 350;
default -> throw new IllegalArgumentException("No such fruit");
};
}
public String getNameOfFruit(int fruit) {
return switch (fruit) {
case Fruits.APPLE_FUJI -> "Fuji Apple";
case Fruits.APPLE_PIPPIN -> "Pippin Apple";
case Fruits.APPLE_GRANNY_SMITH -> "Granny Smith Apple";
case Fruits.ORANGE_NAVEL -> "Navel Orange";
case Fruits.ORANGE_TEMPLE -> "Temple Orange";
case Fruits.ORANGE_BLOOD -> "Blood Orange";
default -> throw new IllegalArgumentException("No such fruit");
};
}
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Fruits.APPLE_FUJI));
}
}
int가 들어오면 아무거나 받기 때문에 너무 범용적이다.
값을 제한할수가 없다.
과일의 종류가 변경될 때 문제를 찾는 시점이 프로그램을 찾는 도중이다.
enumeration을 사용하면 컴파일 타임에 알 수 있다.

package me.whiteship.chapter06.item34.integer_values;
public class AppleClient {
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Fruits.APPLE_PIPPIN));
}
}
클라이언트 코드
바이트코드

APPLE_PIPPIN이 1이라서 ICONST_1로 나온다.
그래서 값이 변경될 때마다 클라이언트도 같이 빌드를 해줘야 한다.
package me.whiteship.chapter06.item34.enumeration;
public enum Apple {
FUJI, PIPPIN, GRANNY_SMITH;
}
각각의 인스턴스타입이 싱글턴 타입이다.
package me.whiteship.chapter06.item34.enumeration;
public class FruitStore {
public int getPrice(Apple apple) {
return switch (apple) {
case FUJI -> 100;
case PIPPIN -> 200;
case GRANNY_SMITH -> 300;
};
}
public int getPrice(Orange orange) {
return switch (orange) {
case NAVEL -> 150;
case TEMPLE -> 250;
case BLOOD -> 350;
};
}
}
package me.whiteship.chapter06.item34.enumeration;
public class AppleClient {
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Apple.PIPPIN));
System.out.println(Apple.PIPPIN);
}
}
enum에서 정하지 않은 다른 값을 사용 불가하다. 타입안정성이 보장된다. 같은 값을 쓸 수 없다.
package me.whiteship.chapter06.item34.enumeration;
public enum Orange {
NAVEL, TEMPLE, BLOOD;
}
NAVER라는게 없어지는 경우 컴파일 일때 발견 가능하다.

package me.whiteship.chapter06.item34.enumeration_method;
public interface Fruit {
int getPrice();
}
인터페이스로 상속 가능
package me.whiteship.chapter06.item34.enumeration_method;
public enum Apple implements Fruit {
FUJI(100), PIPPIN(120), GRANNY_SMITH(140);
private int price;
Apple(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
package me.whiteship.chapter06.item34.enumeration_method;
public enum Orange implements Fruit {
NAVEL(40), TEMPLE(35), BLOOD(20);
private final int price;
Orange(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
package me.whiteship.chapter06.item34.enumeration_method;
public class FruitStore {
public int getPrice(Fruit fruit) {
return fruit.getPrice();
}
}
package me.whiteship.chapter06.item34.enumeration_method;
public class AppleClient {
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Apple.PIPPIN));
System.out.println(Apple.PIPPIN);
}
}
package me.whiteship.chapter06.item34.planet;
// 코드 34-3 데이터와 메서드를 갖는 열거 타입 (211쪽)
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // 질량(단위: 킬로그램)
private final double radius; // 반지름(단위: 미터)
private final double surfaceGravity; // 표면중력(단위: m / s^2)
// 중력상수(단위: m^3 / kg s^2)
private static final double G = 6.67300E-11;
// 생성자
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
package me.whiteship.chapter06.item34.planet;
// 어떤 객체의 지구에서의 무게를 입력받아 여덟 행성에서의 무게를 출력한다. (212쪽)
public class WeightTable {
public static void main(String[] args) {
double earthWeight = 10;
double mass = earthWeight / Planet.EARTH.surfaceGravity();
Planet[] values = Planet.values();
for (Planet p : values)
System.out.printf("%s에서의 무게는 %f이다.%n", p, p.surfaceWeight(mass));
}
}
values가 적어준 순서대로 값을 준다.
package me.whiteship.chapter06.item34.enumeration_method;
public class AppleClient {
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Apple.PIPPIN));
// System.out.println(Apple.PIPPIN);
for (Apple apple : Apple.values()) {
System.out.println(apple);
}
}
}

순서에 의존해서 나오기는 하지만 순서가 바뀔 수도 있기 때문에 그것에 의존해서 작성하지 말라는 말이다.
순서가 계속해서 보장된다는 것을 제약할 수 가 없다.

package me.whiteship.chapter06.item34.operation;
import me.whiteship.chapter06.item34.enumeration.Orange;
import java.util.HashMap;
import java.util.Map;
public enum OperationOrigin {
PLUS, MINUS, TIMES, DIVIDE, NEW_OPERATION;
// enum에서는 생성자가 호출되는 시점에는 정적인 필드들이 초기화 되지 않은 상태이다.
// 따라서 enum 상수에도 접근할 수 없다.
// private static final Map<String, OperationOrigin> stringToEnum = new HashMap<>();
//
// OperationOrigin() {
// stringToEnum.put(toString(), this);
// }
public double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new IllegalArgumentException("not supported operation");
}
}
메소드 하나에서 계산 방식이 다르다. 새로운 오퍼레이션이 있는데 여기에 로직을 추가하는 걸 깜빡할 수도 있지 않겠느냐?
switch case문으로 작성하는 경우 NEW_OPERATION이라는게 새로 있어도 컴파일 오류가 발생하지 않는다.
package me.whiteship.chapter06.item34.operation;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toMap;
// 코드 34-6 상수별 클래스 몸체(class body)와 데이터를 사용한 열거 타입 (215-216쪽)
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
// 코드 34-7 열거 타입용 fromString 메서드 구현하기 (216쪽)
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(
toMap(Object::toString, e -> e));
// 지정한 문자열에 해당하는 Operation을 (존재한다면) 반환한다.
public static Optional<Operation> fromString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
public static void main(String[] args) {
double x = 10;
double y = 5;
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
추상화 메서드를 활용하면 apply를 구현해야만 한다.
NEW_OPERATION("new") {
public double apply(double x, double y) { return x / y; }
};
를 강제할 수 있다.
package me.whiteship.chapter06.item34.enumeration_method;
public class AppleClient {
public static void main(String[] args) {
FruitStore fruitStore = new FruitStore();
System.out.println(fruitStore.getPrice(Apple.PIPPIN));
// System.out.println(Apple.PIPPIN);
for (Apple apple : Apple.values()) {
System.out.println(apple);
}
// System.out.println(Apple.PIPPIN);
Apple pippin = Apple.valueOf("PIPPIN");
System.out.println(pippin);
}
}
Apple.PIPPIN.name()은 String으로 PIPPIN이 나온다.
valueOf는 STRING -> ENUM으로 나온다.
package me.whiteship.chapter06.item34.operation;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toMap;
// 코드 34-6 상수별 클래스 몸체(class body)와 데이터를 사용한 열거 타입 (215-216쪽)
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
// 코드 34-7 열거 타입용 fromString 메서드 구현하기 (216쪽)
private static final Map<String, Operation> stringToEnum =
Stream.of(values()).collect(
toMap(Object::toString, e -> e));
// 지정한 문자열에 해당하는 Operation을 (존재한다면) 반환한다.
public static Optional<Operation> fromString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
public static void main(String[] args) {
double x = 10;
double y = 5;
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
toString을 재정의를 한 경우 "+" -> OPERATION.PLUS, "-" -> OPERATION.MINUS 를 fromString이라는 걸로 바꿔준다는 것이다.
stringToEnum을 String을 ENUM map으로 만들어준다. 그래서 거기서 꺼내온다.

package me.whiteship.chapter06.item34.operation;
import me.whiteship.chapter06.item34.enumeration.Orange;
import java.util.HashMap;
import java.util.Map;
public enum OperationOrigin {
PLUS, MINUS, TIMES, DIVIDE, NEW_OPERATION;
// enum에서는 생성자가 호출되는 시점에는 정적인 필드들이 초기화 되지 않은 상태이다.
// 따라서 enum 상수에도 접근할 수 없다.
// private static final Map<String, OperationOrigin> stringToEnum = new HashMap<>();
//
// OperationOrigin() {
// stringToEnum.put(toString(), this);
// System.out.println(MINUS.toString()); 도 안된다.
// }
public double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new IllegalArgumentException("not supported operation");
}
}
enum static필드가 생성자가 호출된 다음에 초기화가 되기 때문에 생성자 시에는 사용이 안된다.
"+","-"도 static한 필드들이다.

package me.whiteship.chapter06.item34.operation;
import java.util.HashMap;
import java.util.Map;
public class OperationClass {
private final static OperationClass PLUS = new OperationClass();
private final static Map<String, OperationClass> stringToEnum = new HashMap<>();
private OperationClass() {
System.out.println(PLUS.toString());
stringToEnum.put("+", PLUS);
}
}
일반 클래스는 참조가 된다.
enum에서만 안된다.
package me.whiteship.chapter06.item34.payroll;
// 코드 34-8 값에 따라 분기하며 코드를 공유하는 열거 타입. (217쪽)
enum PayrollDayOriginal {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minutesWorked, int payRate) {
int basePay = minutesWorked * payRate;
int overrimePay;
switch (this) {
case SATURDAY: case SUNDAY:
overrimePay = basePay / 2;
break;
default: // 주중
overrimePay = minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
}
return basePay + overrimePay;
}
}
package me.whiteship.chapter06.item34.payroll;
import static me.whiteship.chapter06.item34.payroll.PayrollDay.PayType.WEEKDAY;
import static me.whiteship.chapter06.item34.payroll.PayrollDay.PayType.WEEKEND;
// 코드 34-9 전략 열거 타입 패턴 (218-219쪽)
enum PayrollDay {
MONDAY(WEEKDAY), TUESDAY(WEEKDAY), WEDNESDAY(WEEKDAY),
THURSDAY(WEEKDAY), FRIDAY(WEEKDAY),
SATURDAY(WEEKEND), SUNDAY(WEEKEND);
// (역자 노트) 원서 1~3쇄와 한국어판 1쇄에는 위의 3줄이 아래처럼 인쇄돼 있습니다.
//
// MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
// SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
//
// 저자가 코드를 간결하게 하기 위해 매개변수 없는 기본 생성자를 추가했기 때문인데,
// 열거 타입에 새로운 값을 추가할 때마다 적절한 전략 열거 타입을 선택하도록 프로그래머에게 강제하겠다는
// 이 패턴의 의도를 잘못 전달할 수 있어서 원서 4쇄부터 코드를 수정할 계획입니다.
private final PayType payType;
PayrollDay(PayType payType) { this.payType = payType; }
// PayrollDay() { this(PayType.WEEKDAY); } // (역자 노트) 원서 4쇄부터 삭제
int pay(int minutesWorked, int payRate) {
return payType.pay(minutesWorked, payRate);
}
// 전략 열거 타입
enum PayType {
WEEKDAY {
int overtimePay(int minsWorked, int payRate) {
return minsWorked <= MINS_PER_SHIFT ? 0 :
(minsWorked - MINS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
int overtimePay(int minsWorked, int payRate) {
return minsWorked * payRate / 2;
}
};
abstract int overtimePay(int mins, int payRate);
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minsWorked, int payRate) {
int basePay = minsWorked * payRate;
return basePay + overtimePay(minsWorked, payRate);
}
}
public static void main(String[] args) {
for (PayrollDay day : values())
System.out.printf("%-10s%d%n", day, day.pay(8 * 60, 1));
}
}
enum을 전략으로 만들고, 전략을 선택하는 경우를 선택해서 enum을 2개로 사용한다.
package me.whiteship.chapter06.item34.operation;
// 코드 34-10 switch 문을 이용해 원래 열거 타입에 없는 기능을 수행한다. (219쪽)
public class Inverse {
public static Operation inverse(Operation op) {
switch(op) {
case PLUS: return Operation.MINUS;
case MINUS: return Operation.PLUS;
case TIMES: return Operation.DIVIDE;
case DIVIDE: return Operation.TIMES;
default: throw new AssertionError("Unknown op: " + op);
}
}
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values()) {
Operation invOp = inverse(op);
System.out.printf("%f %s %f %s %f = %f%n",
x, op, y, invOp, y, invOp.apply(op.apply(x, y), y));
}
}
}
이렇게 사용하는 방법이 많다.