티스토리 뷰

기존의 방식

  • 타입 안전을 보장할 방법이 없다.
  • 표현력이 좋지 않다.
  • 동등 연산자 == 로 비교하더라도 아무런 경고가 없다.
  • 문자열로 출력하기가 까다롭다.
// 34-1. 정수 열거 패턴 
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 = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
// 문자열 열거 패턴 
public static final String APPLE_FUJI = "APPLE_FUJI";
public static final String APPLE_PIPPIN = "APPLE_PIPPIN";
public static final String APPLE_GRANNY_SMITH = "APPLE_GRANNY_SMITH";

public static final String ORANGE_NAVEL = "ORANGE_NAVEL";
public static final String ORANGE_TEMPLE = "ORANGE_TEMPLE";
public static final String ORANGE_BLOOD = "ORANGE_BLOOD";
  • 정수 열거 패턴보다 더 안좋다.
  • 하드코딩하게 만든다.
  • 오타가 있어도 컴파일에서 확인이 불가능하니 런타임에 오류가 생긴다.

새로운 방식

// 34-2. 열거 타입
public enum Apple {
    FUJI, PIPPIN, GRANNY_SMITH
}
public enum Orange {
    NAVEL, TEMPLE, BLOOD
}
  • 일종의 클래스이다.
  • 컴파일타임 타입 안전성을 제공한다.
  • 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다.
  • 안에 구조로 보면 이렇게 public static final 로 되어있다.
  • 클라이언트가 인스턴스를 직접 생성하거나 확장할 수 없으니 인스턴드들은 딱 하나씩만 존재한다.
  • 다시말해 인스턴스 통제, 싱글턴이다.
  • 거꾸로 열거 타입은 싱글턴을 일반화한 형태라고 볼 수 있다.
  • 열거 타입에는 toString(), Object 메서드들을 높은 품질로 구현해 놨다.
  • Comparable, Serializable을 구현해놨다.

열거타입의 메서드와 필드

  • 열거 타입에는 어떤 메서드도 추가할 수 있다.
  • 단순하게는 상수 모음일 뿐이지만, 실제로는 클래스이므로 고차원의 추상 개념 하나를 완벽히 표현해낼 수도 있다.
// 34-3. 여덟개의 행성을 나타낸 열거 타입
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;

  private static final double G = 6.67300E-11;

    Planet(double mass, double radius) {
    this.mass = mass;
    this.radius = radius;
    this.surfaceGravity = G * mass / (radius * radius);
  }

    // getter ...

  public double surfaceWeight(double mass) {
      return mass * surfaceGravity;
  }
}
  • 열거 타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스턴스 필드에 저장하면된다.
  • 열거 타입은 근본적으로 불변이라 모든 필드는 final 이어야 한다.
  • 필드는 private 으로 하고 접근자 메서드를 둔다.
  • 열거 타입에서는 values() 를 제공하는데 자신안에 정의된 상수들의 값을 배열에 담아 반환한다.
  • 열거 타입을 선언한 클래스 혹은 그 패키지에서만 유용한 기능은 private이나 package-privat 메서드로 구현한다.

상수별 메서드 구현

public enum Operation {
  PLUS {
    @Override
    public double apply(double x, double y) {
      return x + y;
    }
  },
  MINUS {
    @Override
    public double apply(double x, double y) {
      return x - y;
    }
  },
  TIMES {
    @Override
    public double apply(double x, double y) {
      return x * y;
    }
  },
  DIVIDE {
    @Override
    public double apply(double x, double y) {
      return x / y;
    }
  },
  ;

  public abstract double apply(double x, double y);
}
  • apply() 가 상수 선언 바로 옆에 있으니 새로운 상수를 추가할 때도 apply()를 재정의 해야한다는 사실을 깜빡하기 힘들다.
  • 또한 apply() 가 추상메서드이므로 재정의하지 않았다면 컴파일 오류로 알려준다.

전략 열거 타입

// 급여명세서 
public 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 {
      @Override
      int overtimePay(int minsWorked, int payRate) {
        return minsWorked <= MINS_PER_SHIFT ? 0 :
              (minsWorked - MINS_PER_SHIFT) * payRate / 2;
      }
    },
    WEEKEND {
      @Override
      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);
    }
  }
}
  • 기존의 switch 문은 간결하지만 관리 관점에서는 위험한 코드이다, 왜냐면 새로운 값을 열거 타입에 추가하면 case 문에 잊지 말고 넣어줘야 한다.
  • 이렇게 전략을 선택하도록 하면 switch 문보다 더 안전하고 유연하다.

열거 타입을 언제 쓰라는 건가 ?

  • 필요한 원소를 컴파일타임에 다 알 수 있는 상수 집합일때
  • ex. 태양계 행성, 한 주의 요일, 체스 말...

결론

  • 열거 타입은 확실히 정수 상수보다 더 읽기 쉽고 안전하고 강력하다.
  • 열거 타입에서는 switch 문 대신 상수별 메서드 구현을 사용하자.
  • 열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.