Post

(Effective Java) 6장 enum 타입과 애너테이션 (Annotation)

[Languages & Frameworks/Kotlin Java] - [Java] Enum

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

  • Planet.java
  • 자바에서 enum을 뒷받침하는 아이디어는 다음과 같다. enum 타입 자체는 클래스이며, 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.
1
2
3
4
5
public enum Planet {
    MERCURY(3.3, 2.4);
이는 
public enum Planet {
    public static final Planet MERCURY = new Planet(3.3, 2.4);
  • enum 상수는 값 뿐만 아니라 로직도 가지고 있을 수 있다! abstract를 이용해서.
  • 위 예제에서는 그냥 abstract method를 사용했는데, lambda를 쓰면 더 깔끔하다!!
    • OperationWithLambda.java
    • 단, 람다 코드로 명확히 동작을 알 수 없거나 코드 줄 수가 많아지면 람다를 쓰지 않는게 좋다.
    • 람다 내에서 인스턴스 멤버에 접근해야 하는 경우 람다 대신 abstract method를 사용해야 한다.
      • enum 생성자에 넘겨지는 인수 타입은 컴파일 타임에 추론된다. 반면 인스턴스는 런타임에 만들어진다. 때문에 람다 내에서 인스턴스 멤버에 접근할 수 없다.
  • 아래처럼 enum 값에 따라 로직이 달라야 된다고 하여 switch로 분기하는건 좋지 않다.
    • 유지보수가 어렵다. enum 타입은 추가했는데 case문을 빼먹는 경우가 생길 수 있기 때문.
    • strategy enum 패턴 으로 해결.
1
2
3
4
5
6
7
8
9
10
11
12
int pay(int minutesWorked, int payRate) {
    int basePay = minutesWorked * payRate;

    int overtimePay;
    switch(this) {
        case SATURDAY: case SUNDAY:
            ...
        default:
            ...
    }
    return basePay + overtimePay;
}
  • 다만, switch를 쓰는게 제일 깔끔한 경우도 있다.
    • Inverse.java Operation.java
    • 역연산자를 Operation에 넣자니, Operation의 핵심과는 거리가 좀 있는 부차적인 정보로 보여 Inverse로 별도 분리한 듯.
    • 그러나 inverse() 메서드를 Operation에 넣어도 상관 없어보이긴 한다. 유지보수 측면에서도 그렇고.
    • 만약 switch를 안쓴다면 PLUS(MINUS) 처럼 필드로 지정해버려야 하는데, 이건 Operation의 응집도를 더 해친다.

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

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

  • 비트 필드란 text.applyStyles(BOLD | ITALIC) 이런 식으로 C에서 많이 쓰던 그 것
    • 비트 필드 값을 해석하기 어렵다
    • 비트 필드의 모든 원소를 순회하기 까다롭다
    • API를 수정하지 않고는 비트 수를 늘릴 수 없다
  • 그래서 대신 EnumSet을 쓰자!

아이템 37.

enum을 키로 사용하여 key-value 매핑 해야 하는 경우 EnumMap을 사용하라

1
2
3
4
5
6
7
8
9
10
11
12
public enum Transition {
    // 아래는 나쁜 코드 예시. 대신 EnumMap을 쓰자.
    private static final Transition[][] TRANSITIONS = {
        { null, MELT, SUBLIME },
        { FREEZE, null, BOIL },
        { DEPOSIT, CONDENSE, null }
    };

    public static Transition from(Phase from, Phase to) {
        return TRANSITIONS[from.ordinal()][to.ordinal()];
    }
}

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

  • 매개변수 같은 것 받을 때 enum 타입을 그대로 쓰지 말고, interface로 받아라.
  • enum을 확장해야 하는 경우는 잘 없지만, 위와 같은 연산자(+-/*)가 예가 될 수 있음.
    • interface Operation.java
    • enum BasicOperation.java
    • enum ExtendedOperation.java
    • 근데 인터페이스만 공유하는거라 ExtendedOperation을 순회해도 BasicOperation이 가지고 있는 연산자들은 나오지 않는다. 타입 호환이 가능하다는 점에서 의미가 있음

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

  • 명명 패턴이란, 테스트 메서드인 경우 이름을 testXXX로 시작하도록 한다거나… 하는 것을 말한다.
  • 이름을 통해 쓰임을 지정하는건 너무 느슨한 방식이다. 우리에게는 애너테이션이 있으니까 이걸 쓰는게 더 낫다.
  • 상새 내용은 책 참고. 애너테이션을 새로이 정의할 때 참고할만 하다.source

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

  • 바디가 이미 있는 메서드를 재정의할 때는 당연히 애너테이션을 달아야만 함.
  • 추상 메서드를 정의할 때는 애너테이션을 달아도 되고 안달아도 되지만, 일반적으로 그냥 다는게 더 좋은 것 같다.

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

  • 아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 마커 인터페이스(marker interface)라 한다
  • Serializable 인터페이스가 그 예다.
  • 애너테이션이 있는데 마커 인터페이스를 사용해야 하는 경우? 있다. 바로 인터페이스를 매개변수 등 타입으로 쓸 때.
    • 해당 마커 인터페이스를 구현한 클래스들을 인터페이스 타입으로 처리할 수 있다. 애너테이션은 이게 불가함.
This post is licensed under CC BY 4.0 by the author.