enum, 패키지와 import, static 과 final
6. 연습 문제
문제 1
요일 enum Weekday 를 만들어 isWeekend() 와 next() 를 구현하세요. SUNDAY.next() 는 MONDAY 여야 합니다. values() 와 ordinal() 을 이용해 next() 를 두 줄로 작성하되, 이 경우에는 ordinal() 사용이 왜 안전한지 주석으로 설명하세요.
정답 보기
public enum Weekday {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public boolean isWeekend() { return this == SATURDAY || this == SUNDAY; }
public Weekday next() {
Weekday[] all = values();
return all[(ordinal() + 1) % all.length]; // 순환. ordinal 을 "저장" 하지 않고 순서 계산에만 쓰므로 안전하다
}
}ordinal() 이 위험한 것은 값을 밖(DB·파일)에 내보낼 때입니다. 프로그램 안에서 "다음 순서" 를 계산하는 데만 쓰면 선언 순서를 바꿔도 그 순서대로 동작하니 문제가 없습니다.
문제 2
OrderStatus 에 canTransitionTo(OrderStatus target) 를 추가하세요. 허용되는 전이는 CREATED→PAID, CREATED→CANCELLED, PAID→SHIPPED, PAID→CANCELLED, SHIPPED→DELIVERED 뿐입니다.
EnumMap<OrderStatus, Set<OrderStatus>> 로 전이표를 static 필드에 두고, Main 에서 허용·거부 사례를 각각 두 개 출력하세요.
정답 보기
private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = new EnumMap<>(OrderStatus.class);
static {
TRANSITIONS.put(CREATED, EnumSet.of(PAID, CANCELLED));
TRANSITIONS.put(PAID, EnumSet.of(SHIPPED, CANCELLED));
TRANSITIONS.put(SHIPPED, EnumSet.of(DELIVERED));
TRANSITIONS.put(DELIVERED, EnumSet.noneOf(OrderStatus.class));
TRANSITIONS.put(CANCELLED, EnumSet.noneOf(OrderStatus.class));
}
public boolean canTransitionTo(OrderStatus target) { return TRANSITIONS.get(this).contains(target); }
// Main
System.out.println(CREATED.canTransitionTo(PAID)); // true
System.out.println(PAID.canTransitionTo(CANCELLED)); // true
System.out.println(SHIPPED.canTransitionTo(CANCELLED)); // false (배송 시작 후 취소 불가)
System.out.println(DELIVERED.canTransitionTo(PAID)); // falseenum 안의 static 블록은 상수가 모두 만들어진 뒤 실행되므로 상수를 참조해도 됩니다. 반대로 enum 생성자 안에서는 static 필드를 참조할 수 없습니다(아직 초기화 전). 전이표를 생성자 인자로 넣으려다 순환 참조 오류를 만나면 이 규칙 때문입니다.
문제 3
Counter 처럼 생성 수를 세는 클래스에서 created 를 static 이 아닌 인스턴스 필드로 바꾸면 출력이 어떻게 달라지는지 예측하고 실행으로 확인하세요. 그런 다음 Counter.created() 를 synchronized 없이 여러 스레드에서 동시에 new Counter() 하면 무슨 문제가 생길 수 있는지 한 문장으로 쓰세요(구현은 고급 멀티스레드 레슨에서 다룹니다).
정답 보기
private int created = 0; 으로 바꾸면 객체마다 자기 created 를 갖게 되어 세 객체 모두 id=1, created=1 이 됩니다. 공유하는 것이 없으니 "지금까지 몇 개" 를 셀 수 없습니다.
동시성 문제: ++created 는 읽기·더하기·쓰기 세 단계라, 두 스레드가 동시에 실행하면 둘 다 같은 값을 읽어 id 가 중복되고 created 가 하나 덜 셉니다. 고급 레슨의 AtomicInteger 나 synchronized 가 해법입니다.