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

 

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

  • Planet.java
  • 자바에서 enum을 뒷받침하는 아이디어는 다음과 같다. enum 타입 자체는 클래스이며, 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.

```java

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 패턴으로 해결.

```java

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 메서드 대신 인스턴스 필드를 사용하라

  • ``java ordinal()`` 메서드는 해당 상수가 그 enum에서 몇 번째 위치인지를 반환한다.
  • 그래서 상수 위치가 변경되면 ordinal 값도 바뀐다! 또는 불연속적인 값이면 중간에 dummy 상수를 넣어야 하는 등 좋지 않다.
  • ordinal 쓰지 말고 그냥 숫자 필드 하나 파서 쓰자.

 

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

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

 

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

```java

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`` 인터페이스가 그 예다.
  • 애너테이션이 있는데 마커 인터페이스를 사용해야 하는 경우? 있다. 바로 인터페이스를 매개변수 등 타입으로 쓸 때.
    • 해당 마커 인터페이스를 구현한 클래스들을 인터페이스 타입으로 처리할 수 있다. 애너테이션은 이게 불가함.