본문 바로가기

STUDY REVIEW/이펙티브 자바 독서스터디

[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 6장 열거 타입과 애너테이션

6장 열거 타입과 애너테이션
아이템 34. int 상수 대신 열거 타입을 사용하라
아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라
아이템 36. 비트 필드 대신 EnumSet을 사용하라
아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라
아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
아이템 39. 명명 패턴보다 애너테이션을 사용하라
아이템 40. @Override 애너테이션을 일관되게 사용하라
아이템 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라

읽고 느낀 점

이직을 하고 나서 몸이 지쳐서 인지 아니면 이직을 했다는 자만때문인지 나태해지기 시작했다.
집에 돌아오면, 아무것도 하기 싫어서 책을 구석에 밀어내며 회사에서 굽혀있던 척추를 펴주었다.
쉬고 싶다는 생각이 강해지며  번아웃이 왔다.

같이 스터디 하시는 분의 배려로 일주일 아니 2주일을 놀았다.
그리고 회사에 헤드리스트를 요청했으나 거절당했다.
더 나아지고 싶은 계기가 생겼다.
번아웃이 해결되었다ㅋ


비록 이 회사에서 enum을 사용했을때의 '아 드디어 나도 기술스택을 제대로 쌓을 수 있겠다'라는 생각을 했지만,
요즘 enum을 사용 안하는 회사가 있을까? 하는 의문이 들었다.

어서 공부해서 나아가자.
나아가는 김에 enum에 대해 정리한다.


아이템 34. int 상수 대신 열거 타입을 사용하라

단순한 열거 타입
public enum Apple {FUJI, PIPPIN, GRANNY_SMITH} public enum Orange {NAVEL, TEMPLE, BLOOD}

상수별 메서드 구현

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 메서드 구현하기 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)); }

전략 열거 타입 패턴

enum PayrollDay { MONDAY(WEEKDAY), TUESDAY(WEEKDAY), WEDNESDAY(WEEKDAY), THURSDAY(WEEKDAY), FRIDAY(WEEKDAY), SATURDAY(WEEKEND), SUNDAY(WEEKEND); private final PayType payType; PayrollDay(PayType payType) { this.payType = payType; } 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)); } }
  • 열거 타입은 정수 상수보다 사용성이 더 뛰어나다.
  • 열거 타입은 명시적 생성자나 메서드 없이 쓰이지만, 상수를 데이터와 연결짓거나 상수마다 각각 동작할때 좋다.
  • swich문 대신 상수별 메소드 구현을 이용하자
  • 열거타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.

아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라


ordinal() : 해당 상수가 열거타입에서 몇번째 위치하는지 반환

아래의 코드는 잘못되었으니 절대 따라하지 말 것!

public enum Ensemble{ SOLO, DUET, TRIO, QUARTET, ..., OCTET; public int numberOfMusicians(){ return ordinal() + 1; } }

대신 인스턴스 필드를 이용하자

public enum Ensemble{ SOLO(1), DUET(2), TRIO(3), QUARTET(4); private final int numberOfMusicians; Ensemble(int size) { this.numberOfMusicians = size; } public int numberOfMusicians(){ return numberOfMusicians; } }

아이템 36. 비트 필드 대신 EnumSet을 사용하라


코드도 트렌드가 있다.
모르면 최신 트렌트를 따라하자.

public class Text { public static final int STYLE_BOLD = 1 << 0; // 1 public static final int STYLE_ITALIC = 1 << 1; // 2 public static final int STYLE_UNDERLINE = 1 << 2; // 4 public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 // 매개변수 styles는 0개 이상의 STYLE_ 상수를 비트별 OR한 값이다. public void applyStyles(int styles) {...} } text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

위 코드는 한 물 지난 코드이다.
아래의 코드를 이용하자.

public class Text { public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } public void applyStyles(Set<Style> styles) {...} } text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

유일한 단점은 불변 EnumSet을 만들지 못한다는 것이다.
향후 릴리스의 업데이트를 기다리며 Collections.ummodifiableSet으로 감싸 사용할 수 있다.


아이템 37. ordinal 인덱싱 대신 EnumMap을 사용하라


ordinal 인덱싱

Set<Plant>[] plantByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length]; for (int i = 0; i < plantsByLifeCycle.length; i++) { plantsByLifeCycle[i] = new HashSet<>(); } for (plant p : garden) { plantsByLifeCycle[p.lifeCycle.ordinal()].add(p); } // 결과 출력 for (int i = 0; i < plantsByLifeCycle.length; i++) { System.out.printf("%s: %s%n", Plant.LifeCycle.values()[i], plantsByLifeCycle[i]); }

EnumMap : 열거 타입을 키로 사용하도록 설계된 아주 빠른 Map 구현체

Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class); for (Plant.LifeCycle lc : Plant.LifeCycle.values()) { plantsByLifeCycle.put(lc, new HashSet<>()); } for (Plant p : garden) { plantsByLifeCycle.get(p.lifeCycle).add(p); } System.out.println(plantsByLifeCycle);

아이템 38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라


Enum 자체를 확장할 수 없지만, 인터페이스와 인터페이스를 구현하는 기본 열거 타입을 사용해 인터페이스 효과를 낼 수 있다.

public interface Operation { double apply(double x, double y); } public enum BasicOperation implements 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; } Copycopy code to clipboard public enum ExtendedOperation implements Operation { EXP("^") { public double apply(double x, double y) { return Math.pow(x, y); } }, REMAINDER("%") { public double apply(double x, double y) { return x % y; } }; private final String symbol; } Copycopy code to clipboard public static void main(String[] args) { double x = Double.parseDouble(args[0]); double y = Double.parseDouble(args[1]); test(ExtendedOperation.class, x, y); } private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) { for (Operation op : opEnumType.getEnumConstants()) { System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); } }

아이템 39. 명명 패턴보다 애너테이션을 사용하라


@Test
@Retention
@Target
javax.annotation.processing API 문서 참조

// 코드 39-1 마커(marker) 애너테이션 타입 선언 (238쪽) import java.lang.annotation.*; /** * 테스트 메서드임을 선언하는 애너테이션이다. * 매개변수 없는 정적 메서드 전용이다. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { } // 코드 39-2 마커 애너테이션을 사용한 프로그램 예 (239쪽) public class Sample { @Test public static void m1() { } // 성공해야 한다. public static void m2() { } @Test public static void m3() { // 실패해야 한다. throw new RuntimeException("실패"); } public static void m4() { } // 테스트가 아니다. @Test public void m5() { } // 잘못 사용한 예: 정적 메서드가 아니다. public static void m6() { } @Test public static void m7() { // 실패해야 한다. throw new RuntimeException("실패"); } public static void m8() { } } public class RunTests { public static void main(String[] args) throws Exception { int tests = 0; int passed = 0; Class<?> testClass = Class.forName(args[0]); for (Method m : testClass.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { tests++; try { m.invoke(null); passed++; } catch (InvocationTargetException wrappedExc) { Throwable exc = wrappedExc.getCause(); System.out.println(m + " 실패: " + exc); } catch (Exception exc) { System.out.println("잘못 사용한 @Test: " + m); } } } System.out.printf("성공: %d, 실패: %d%n", passed, tests - passed); } }

아이템 40. @Override 애너테이션을 일관되게 사용하라


상위 클래스의 메서드를 재정의하려는 모든 메서드에 @Override 애너테이션을 달자.
의식적으로 달다보면 내가 실수했을때 컴파일러가 알려준다.
예외는 단 한가지뿐.
구체 클래스에서 상위 클래스의 추상 메서드를 재정의한 경우엔 달지 않아도 상관없다.


아이템 41. 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라

  • 마커 인터페이스 : 새로 추가하는 메서드 없이 타입정의가 목적일 경우
  • 마커 애너테이션 : 클래스나 인터페이스 외의 프로그램 요소에 마킹하거나 애너테이션을 활용하는 프레임워크에 그 마커를 편입하고자 한다.


적용 대상이 ElementType.TYPE 인 마커 애너테이션을 작성한다면 마커 인터페이스인지 애너테이션인지 고민할 것