6. Enums 타입과 어노테이션
Java는 두 가지 특수 목적의 참조 유형 패밀리를 지원합니다.
- enum
- annotation
아래에서는 이를 이용하는 좋은 사례입니다.
#
Item 34. 상수형 대신 열겨형을 사용합니다.Enum 형은 고정 세트로 구성된 유형이며, 이는 형식에 대한 안전성을 제공합니다. Java의 Enum 형의 기본 개념은 public static final field
를 통해서 각각의 Enum 상수를 하나의 인스턴스로 내보내는 클래스입니다. 특히 이에 대한 액세스가 불가능하기 때문에 수정할 수 없습니다.
아래는 간단한 Enum 형 타입입니다.
아래는 data와 behavior이 있는 열거형 타입입니다.
이러한 코드에서 데이터를 Enum 상수와 연결하려면 인스턴스 필드를 선언하고 데이터를 가져와 필드에 저장하는 생성자를 작성해줘야 합니다.
Enum 상수와 메소드를 결합한 코드를 작성할 수도 있습니다.
전략적으로 사용하는 enum 패턴 코드는 다음과 같습니다. (근로자 근무 급여 계산 메서드)
열거형의 Switch는 상수 특정 behavior을 enum types을 늘리는데 유용합니다.
컴파일 타임에 멤버가 알려진 상수 집합이 필요할 때마다, Enum 형을 사용하는 것이 좋습니다. 다만, 열거형 유형의 상수 집합이 항상 고정되어 있을 필요는 없습니다.
이를 정리하면 다음과 같습니다. int 상수에 비해 Enum 형은 더 읽기 쉽고 안전하며, 강력합니다.
#
Item 35. Ordinals 대신에 인스턴스 필드를 사용합니다.많은 Enum 형은 Int와 관련되어 있으며, ordinal 등을 통해서 위치를 반환할 수 있습니다. 그러나 이를 남용하면 유지보수 및 관리에서 문제가 생길 수 있습니다.
이러한 코드를 해결하는 방법은, 파생하지 않고 인스턴스 필드에 저장하는 것입니다.
EnumSet
을 사용합니다.#
Item 36. 비트 필드 대신에 비트 필드 표현을 통하면 비트 연산을 통해서 합집합이나 교차 집합 연산을 효율적으로 계산할 수 있습니다. 그러나 이러한 방법들은 int형 상수이 가지고 있는 단점이 있기 때문에, java.util
패키지의 EnumSet
을 사용하는 것이 중요합니다.
이를 사용한 코드는 다음과 같습니다.
즉, 이를 요약하면, Enum 형이 집합에서 사용되기 때문에 비트 필드로 표현할 이유가 없습니다.
Ordinals Indexing
대신 EnumMap
을 사용합니다.#
Item 37. 때때로 ordinal 메서드를 사용해서 배열로 인덱싱 하는 코드를 볼 수 있습니다.
이는 그러한 경우의 예시입니다.
이러한 코드를 배열로 인덱싱한 코드입니다. (잘못된 코드)
이러한 코드는 문제가 있습니다. 배열은 제네릭과 호환되지 않기 때문에 깔끔하게 컴파일되지 않습니다.그리고, 사용자가 인덱싱 배열을 사용할 때 신경을 써야하는 부분이 많습니다.
짧은 코드를 통해서 이보다 좀 더 좋은 코드를 구성하는 것은 EnumMap
을 사용하는 것입니다.
두 개의 Enum 형을 사용하는 경우에도 EnumMap을 사용하는 것이 좀 더 안전성이 높습니다.
위으 코드의 경우에는 오류가 발생할 가능성이 거의 없으며, 명확성과 안전성 및 유지 관리성을 높이며 공간/시간 비용이 지불되지 않습니다.
따라서, ordinal을 사용해서 배열로 인덱싱하는 것은 적절하지 않으며 대신에 EnumMap을 사용하는 것이 중요합니다.
#
Item 38. 인터페이스로 확장 가능한 Enum을 모방합니다.표준 enum을 정의해서 임의의 인터페이스를 구현할 수 있습니다. 이를 표현한 코드는 다음과 같습니다.
이를 확장한 enum입니다.
이를 테스트하는 코드는 다음과 같습니다.
이 방법이 아니더라도, 아래처럼 Collection<? extends Operation> Class T
를 사용할 수 있습니다. 이는 덜 복잡하고 유연합니다. (다만, EnumSet이나 EnumMap을 사용할 수 없는 코드입니다.)
두 코드 모두 결과값은 이와 같습니다.
결론적으로 확장 가능한 Enum 유형을 작성할 수는 없지만, 인터페이스를 구현하는 Enum 타입과 함께 제공되는 인터페이스를 작성해서 동작시킬 수 있습니다.
Naming Patterns
보다 Annotation
을 선호합니다.#
Item 39. 기존의 Naming Patterns의 문제는 다음과 같습니다.
- 기존의 JUnit3의 경우, 메서드 명이 test로 시작하지 않으면 실패합니다.
- 적절한 프로그램 요소에서만 사용되도록 할 수 없습니다.
- 매개 변수 값을 프로그램 요소와 연관시키는 좋은 방법을 제공하지 않습니다.
JUnit4부터는 annotation을 통해서 테스트 프레임 워크를 구성할 수 있게 되었습니다.
이를 사용하는 코드가 아래와 같이 있을 때, 되는 코드와 안되는 코드를 보면 그 차이를 확인할 수 있습니다.
이 위의 Sample 클래스에서 Test 어노테이션이 포함된 테스트 메서드는 4가지이나, m5은 static을 붙이지 않았기 때문에 유효하지 않습니다. m1만 성공을 하고, m3과 m7은 실패하게 됩니다.
이를 아래의 코드로 실행할 수 있습니다.
이외에도 여러 테스트 코드 및 어노테이션을 사용하는 방법이 있습니다. 다중 어노테이션이나, 특정 에러만 동작하게 하는 어노테이션을 구성할 수도 있습니다. 이를 다 작성하기에는 내용이 많아서 작성하지는 않겠습니다.
이러한 어노테이션에서의 핵심은 다음과 같습니다.
- 어노테이션을 사용할 수 있는 경우에는, Naming Patterns 을 사용할 필요가 없습니다.
- 모든 프로그래머는 Java가 제공하는 사전 정의된 어노테이션을 사용하는 것이 중요합니다.
- 또한 IDE나 분석 툴에서 제공하는 어노테이션을 사용하는 것이 중요합니다.
Override
어노테이션을 일관되게 사용해야합니다.#
Item 40. Java 라이브러리에서는 여러 어노테이션이 포함되어 있는데, 그중에서 중요한 어노테이션으로 @Override
를 고를 수 있습니다. @Override
는 메서드 선언에서만 사용할 수 있으며, 어노테이션이 달린 메서드 선언이 상위 유형을 재정의 함을 나타냅니다. 이를 지속적으로 사용하면 많은 종류의 버그를 예방할 수 있습니다.
이를 보여주는 코드는 다음과 같습니다.
위 코드는 그냥 보면, 문제를 인지할 수 없습니다. 위 코드는 2개의 동일한 소문자로 이루어진 26개의 Bigram을 Set에 반복적으로 추가합니다. (Set은 집합이므로) 26세트가 나와야한다고 생각하지만, 위의 코드는 260세트가 나오게 됩니다.
위의 코드에서의 문제는 equals를 오버로딩하지 않아 그렇습니다.
이와 같이 구성할 때, 생각했던 26세트가 나오게 됩니다.
즉, super class를 재정의하는 경우 생각하는 모든 메서드 선언에 Override
어노테이션을 사용해야합니다. Override
어노테이션을 통해서 많은 오류로 부터 사용자를 보호할 수 있습니다.
Marker Interface
를 사용합니다.#
Item 41. 타입을 정의하기 위해 Marker Interface
는 메서드를 포함하지 않고 일부 구현 속성을 가지는 인터페이스입니다. (Ex. Serializable
인터페이스)
Marker Interface
는 Marker Annotation
보다 2가지의 장점이 있습니다.
marker interface
는 표시된 클래스의 인스턴스에 의해 구현되는 유형을 정의합니다.- 이를 통해서 런타임까지 잡을 수 없는 에러를 컴파일 타임에 잡을 수 있습니다.
marker interface
는marker interface
보다 더 정확하게 타겟팅할 수 있습니다.market annotation
은 타겟으로 적용해야하는 반면에,marker interface
는 인터페이스를 확장하여, 적용할 수 있습니다.
이에 반해 Marker Annotation
의 장점은 어노테이션의 일부라는 것입니다. 그렇기 때문에 Marker Annotation
은 어노테이션 기반 프레임 워크의 일관성을 위해 사용할 때 좋습니다.
#
Marker Interface와 Marker Annotation의 사용 경우.Marker Interface
- 새로운 메서드가 연결되지 않은 유형과 정의하는 경우
- 클래스와 인터페이스에서 적용되는 경우, 하나 이상의 메서드에서 필요하다고 판단되는 경우
Marker Annotation
- 클래스 및 인터페이스 이외의 프로그램 요소를 표시하는 경우
- 어노테이션을 많이 사용하는 프레임 워커에 마커를 맞추려는 경우
Marker Interface (마커 인터페이스, 태그 인터페이스)
내부에 메서드나 상수가 없는 인터페이스
Ex) Serializable
인터페이스, Clonable
인터페이스
Marker Annotation (마커 어노테이션)
멤버를 포함하지 않으며 데이터로 구성되지 않으며, 그저 어노테이션 선언을 표시하기 위해 존재합니다.
Ex) @Override