ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아이템34. int 상수 대신 열거 타입을 사용하라
    책/이펙티브자바 2021. 8. 28. 17:34

    기존의 방식

    • 타입 안전을 보장할 방법이 없다.
    • 표현력이 좋지 않다.
    • 동등 연산자 == 로 비교하더라도 아무런 경고가 없다.
    • 문자열로 출력하기가 까다롭다.
    // 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 문 대신 상수별 메서드 구현을 사용하자.
    • 열거 타입 상수 일부가 같은 동작을 공유한다면 전략 열거 타입 패턴을 사용하자.
킹수빈닷컴