Effective Java 5 | Enum, Annotation

공부의 기록·2021년 12월 13일
0

Java (Effective Java)

목록 보기
6/12
post-thumbnail

열거형, 어노테이션

본 문서는 2021년 12월 22일 에 작성되었습니다.

열거형 은 매우 매력적인 타입입니다.
카테고리 와 같이 고정되어있는 데이터들을 상수로 넘길 때와 문자열로 넘길 때의 장단점을 모두 해결하는 강력한 힘을 가지고 있습니다. 이에 대해서는 Java : Enum을 참고해주세요.

특히 Java 열거형 은 값 뿐만 아니라 열거형 본인의 타입까지 비교합니다.
이로 인해 직관적이면서도 안정적으로 작용을 합니다.
그렇기 때문에 Effective 시리즈 곳곳에서 열거형을 사용하고 있었습니다.

하지만 내가 열거형을 사용하려고 하면 막상 예시가 떠오르지 않았습니다.
따라서 이번 챕터를 진행하며 열거형의 규칙이나 기법에 대한 응용을 보고
작은 아이디어를 얻어갔으면 좋겠습니다.


열거형

본 내용은 2021년 12월 22일 에 작성되었습니다.

여기서는 다음과 같은 내용을 다루고 있습니다.

  1. 열거형 권장
  2. ordinal() vs 인스턴스 필드
  3. EnumSet 권고
  4. EnumMap 권고
  5. 확장성 열거타입

아이템 34 | 열거형 권장

# 열거형의 사용 사례는 3가지로 나눌 수 있을 것 같습니다.

  1. 단순 열거형 변수들의 집합
  2. n개의 멤버변수를 가지고 있는 열거형 변수들의 집합
  3. n개의 멤버변수와 n개의 메서드를 가지고 있는 열거형 변수들의 집합

# 다만 열거형 을 만드는데 주의할 점은 다음과 같습니다.

  1. 열거형은 불변 객체이다.
  2. 열거형이 가지고 있는 모든 필드는 final 이어야 한다
  3. 열거형의 필드는 private 로 하고 접근 메서드(getter) 를 쓰는 것이 낫다.
  4. 열거형의 정보로 어떤 것을 연산해야 한다면 최적화를 위해 선언 시점에 연산하여 객체에 넣는 것도 좋다.

# 그러나 당신이 열거형 타입마다 같은 메서드에 다른 기능을 부여해주고 싶으면 어떻게 해야할까요? 사용가능한 방법은 2가지가 있습니다.

  1. 사용하는 부분에서 switch(열거형타입) 을 이용한 분기점 처리
  2. 열거형 선언 시 abstract method 선언

1번 방법으로 할 시, case 열거형: 을 추가하지 않으면 예상하지 못한 결과를 받을 수 있다.
2번 방법으로 할 시, 추상 메서드의 생성 강제로 절대로 잊어버리지 않을 수 있다.

public enum Operation {
   PLUS {
     public double apply(double x,double y) {
        return x+y;
     }
   },
   MINUS { /* return 프로세스 제외하고 동일 */ }
   TIMES { /* return 프로세스 제외하고 동일 */ }
   DIVIDE { /* return 프로세스 제외하고 동일 */ }
   
   public abstract double apply(double x, double y);
}

# 때로는 카테고리 안에서 카테고리를 분류해야 할 수도 있습니다.

예상 가능한 프로세스는 다음과 같습니다.

  1. 일주일을 표현한 열거형을 작성하고
  2. 이를 다시 평일과 주말로 나누어서
  3. 평일과 주말에 다른 프로세스를 적용하는 것
public enum Day {
    // 1차 카테고리 열거형 선언
    MONDAY(WEEKDAY),
    TUESDAY(WEEKDAY),
    WEDNESDAY(WEEKDAY),
    THURSDAY(WEEKDAY),
    FRIDAY(WEEKDAY),
    SATURDAY(WEEKEND),
    SUNDAY(WEEKEND);
    
    Day(DayCategory dayCategory){
       this.dayCategory=dayCategory;
    }
    
    // 1차 카테고리 열거형이 가지게 될 2차 카테고리 열거형 선언
    private final DayCategory dayCategory;
    enum DayCategory {
       WEEKDAY{
          int overtimePay(int workMins, int hourWedge){
             return (workMins <= MINS_PERSHIFT) ? 0 : (workMins - MINS_PER_SHFIT) * hourWedge / 2;
          }
       },
       WEEKEND{
          int overtimePay(int workMins, int hourWedge){
             return workMins * hourWedge / 2;
          }
       };
       
       // 초고근무수당 계산(근무분 * 시간당 돈)
       asstract int overtimePay(int workMins, int hourWedge);
       
       int totalPay(int workMins, int hourWedge) {
          int basicPay=workMins*hourWedge;
          return basicPay+overtimePay(workMins,hourWedge);
       }
    }
    // 2차 카테고리 열거형 타입을 매개변수로 받는 1차 카테고리 열거형 생성자
}

아이템 35 | ordinal() vs 인스턴스 필드

원제목 | ordinal() 메서드 대신 인스턴스 필드를 사용하라

대부분 열거 타입 상수는 자연스럽게 하나의 정수값에 대응된다.
그리고 모든 열거 타입 상수는 ordinal() 로 몇 번째 위치인지 출력할 수 있다.

그러나 ordinal() 이라는 메서드에 의존하면 제한적인 것들이 많다.
아래의 열거형에 세쌍둥이을 넣고싶으면 아이세명과 대응되는 상수 값이 겹쳐지는 것이다.

public enum Ensemble {
   아이한명, 아이두명, 아이세명, 아이네명;
   
   public int numberOfKid() {return ordinal()+1; }
}

그렇기 때문에 열거 타입 상수의 필드(Field, 바디)를 따로 열어서 그 안에 변수를 넣어두는 것이 합당하다.
Enum API 에서는 ordinal() 의 설계 목적이 EnumSet 과 EnumMap 과 같은 열거형 범용 자료구조를 위함이며, 대부분의 프로그래머가 직접적으로 사용할 일은 없다 라고 적혀있다.

public enum Esemble {
   아이한명(1), 아이두명(2), 쌍둥이(2), 아이세명(3), 세쌍둥이(3), 아이네명(4);
   
   private final int countOfKids;
   public int countOfKid() { return countOfKids; }
   
   Esemble(int sizeOfValue) {
     this.countOfKids=sizeOfValue;
   }
}

아이템 36 | EnumSet 권고

원제목 | 비트 필드 대신 EnumSet 을 사용하라

# 비트필드

일반적인 사칙연산자 말고도 비트연산은 강점을 가지고 있습니다.
이러한 비트 연산들을 한 필드 안에 선언해둔 것을 비트 필드 라고 하며,
이를 통해서 특정한 연산 (ex. 합집합과 교집합 (집합연산) ) 을 효율적으로 수행할 수 있습니다.

public class bitOperation {
   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
}

그러나 이러한 상수 선언형 비트 필드는 다음과 같은 단점을 가지고 있습니다.

  1. 정수 열거 상수의 단점을 그대로 지니고 있다.
  2. 비트 필드 값이 그대로 출력되면 단순한 정수 열거 상수를 출력할 때보다 해석하기가 훨씬 어렵다.
  3. 비트 필트 하나에 녹아 있는 모든 원소를 순회하기가 어렵다.
  4. 최대 몇 비트가 필요할 지 API 작성 시점에 예상할 수 있어야 한다.

# EnumSet

EnumSet 은 다음과 같은 특징이 있습니다.

  1. 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다.
  2. Set 인터페이스를 완벽히 구현한다
  3. 타입 안전하다
  4. 어떤 Set 구현체와도 함께 사용할 수 있다.

EnumSet 만 가지는 특징이 있습니다.

  1. 원소가 총 64개 이하라면 대부분의 경우에 EnumSet 전체를 long 변수 하나로 표현하여 비트 필드에 비견되는 성능을 보여준다.
  2. removeAll retainAll 같은 대량작업은 비트를 효율적으로 처리할 수 있는 산술연산을 사용한다.
  3. 비트를 다룰 때 만날 수 있는 오류들에서 해방된다

EnumSet 의 단점은 다음과 같습니다.

  1. jdk 11 까지도 불변객체 스타일로 만들어지지 않았다.

위 단점은 다음과 같은 방법으로 해결할 수 있지만 명확성과 성능이 희생됩니다.

  1. Collections.unmodifiableSet 으로 EnumSet 을 감싸 사용할 수 있다.

아이템 37 | EnumMap (미구현)

아이템 38 | 확장성 열거타입 (미구현)


어노테이션

본 문서는 2021년 12월 23일 에 작성되었습니다.

우리가 쉽게 경험할 수 있는 @Override 같은 어노테이션부터 다양한 것들에 대한 내용을 담고 있습니다.

아이템 39 | 명명패턴 vs 어노테이션

원제목 | 명명 패턴 보다 어노테이션을 사용하라

본 내용은 JUnit 4 와 관련된 어노테이션을 예로 들고 있습니다.
아직 JUnit 4 를 배우지 않은 관계로 본 내용은 넘어갔습니다.

아이템 40 | @Override

원제목 | @Override 어노테이션을 일관되게 사용하라

@Override 는 부모 클래스의 메서드를 자식 클래스에서 재정의했음을 알리는 어노테이션이다.
컴파일러는 해당 어노테이션을 통해 당신이 실수했을 때 바로 알려줄 것입니다.

따라서 모든 재정의 메서드에 @Override 를 붙이도록 해야 합니다.
또한 이클립스 같은 IDEs 를 이용한다면 환경 설정을 통해 @Override 를 강제할 수도 있습니다.

유일하게 @Override 를 붙여도 되지 않는 경우는
부모 클래스의 추상 메서드를 재정의하는 순간입니다.
이 경우는 어차피 구현하지 않을 경우에 에러가 발생하므로 @Override 를 쓰지 않아도 됩니다.

하지만 일관성 있게 붙이는 것이 보기 좋다면 붙이지 않아도 됩니다.

아이템 41 | 마커 인터페이스 vs 마커 어노테이션

원제목 | 정의하려는 것이 타입이라면 마커 인터페이스를 이용해라

마커 인터페이스 는 아무 (추상) 메서드가 없는 인터페이스 입니다.
따라서 자신을 구현하는 클래스가 특정 속성임을 의미하기만 합니다.

마커 어노테이션 은 이러한 기능을 어노테이션의 형태로 하는 것입니다.

하지만 Effective Java 에서는 다음과 같은 이유로 마커 인터페이스를 권고 하고 있습니다.

  1. 구현체의 클래스들을 구분하는 타입으로의 기능성
    1.a. 마커 인터페이스는 타입 구분자로 쓸 수 있다.
    1.b. 마커 어노테이션은 타입 구분자로 슬 수 없다.

  2. 에러의 발생시기
    2.a. 마커 인터페이스는 컴파일 타임 에 에러가 발생한다.
    2.b. 마커 어노테이션은 런타임 에 에러가 발생한다.

그러나 어노테이션은 거대한 어노테이션 시스템의 도움 을 받을 수 있습니다.
따라서 어노테이션을 적극 사용하는 프레임 워크를 사용한다면 권고 될 수 있을 것 같습니다.

아이템 22 | 인터페이스 타입 정의 에서 말한 타입을 정의할 경우에만 인터페이스를 사용하라.
그 외의 값들을 저장하려면 유틸리티 클래스를 사용하라 와 매우 연관성이 깊은 부분이었다.

또한 본 문서의 아이템 34 | 열거형 권장 과도 유사했다.

profile
2022년 12월 9일 부터 노션 페이지에서 작성을 이어가고 있습니다.

0개의 댓글