표식 인터페이스는 아무 메서드도 선언하지 않는 인터페이스다. Serializable 인터페이스가 대표적인 예이다. 그런데, 표식 어노테이션을 쓰게 되면 표식 인터페이스는 필요없는 것이 아닌가?  표식 어노테이션과 비교했을 떄 표식 인터페이스에는 두 가지 장점이 있따. 첫 째, 표식인터페이스는 결국 표식 붙은 클래스가 만드는 객체들이 구현하는 자료형이라는 것이다. 표식 어노테이션은 자료형이 아니다. 자료형이라면, 런타임중에 발견될 오류를 컴파일 시점에 발견할 수 있다. 

 표식 인터페이스가 어노테이션보다 나은 점 두 번째는, 적용 범위를 좀 더 세밀하게 지정할 수 있다는 것이다. 그렇다면 표식 어노테이션의 주된 장점은 무엇일까? 프로그램 안에서 어노테이션 자료형을 쓰기 시작한 뒤에도 더 많은 정보를 추가할 수 있다는 것이다. 이에 반해 표식 인터페이스를 쓰는 경우에는 일단 구현이 이루어지고 난 다음에는 새로운 메서드를 추가하는 것이 일반적으로 불가능하기 때문이다. 인터페이스를 구현하는 모든 클래스에 메서드를 추가해야 하기 때문이다. 


 그렇다면 표식 어노테이션과 표식 인터페이스는 각각 어떤 상황에 걸맞나? 클래스나 인터페이스 이외에 프로그램 요소에 적용되어야 하는 표식은 어노테이션이 적절하다. 이 표식이 붙은 객체만 인자로 받을 수 있는 메서드를 만들 생각이라면, 표식 인터페이스를 써야 한다. 그러면 해당 메서드의인자 자료형으로 해당 인터페이스를 사용할 수 있어서 컴파일 시점에서 오류를 발견할 수 있다.


요약

표식 인터페이스와 표식 어노테이션은 쓰임새가 다르다. 새로운 메서드가 없는 자료형을 정의하고자 한다면 표식 인터페이스를 이용해야 한다. 클래스나 인터페이스 이외의 프로그램 요소에 표식을 달아야 하고, 앞으로 표식에 더 많은 정보를 추가할 가능성이 있다면 표식 어노테이션을 사용해야한다. 


 위 코드의 문제점과 결과값이 무엇이 나올지 예측이 되는가? 아마 결과값으로 26이 찍히기를 예상하겠지만 실제로는 260이 찍힌다. 미묘하게 숨어있는 한 가지 문제점 때문이다. 이 문제점은 바로 equals의 오버라이딩에 숨어있다. 프로그래머는 equals 메소드를 오버라이드 할 목적으로 작성했겠지만, equals의 인자를 보면 Object가 아니라 Bigram이다. 즉 , 오버로딩을 한 것이다. 따라서 equals는 Object에 구현되어 있는 equals를 재사용한 것이며, 따라서 결과값이 260이 나오게 된다. 

 다행인 것은 컴파일러가 이런 오류를 찾을 수 있도록 돕는다는 것인데, 이러한 도움을 받기 위해서는 메소드에 @Override 어노테이션을 추가해야 한다.

위와 같이 수정하면, 컴파일러는 오류 메세지를 출력하게 된다. 즉 상위 클래스에 선언된 메서드를 재정의할 때는 반드시 선언부에 Override 어노테이션을 붙혀야 한다는 것이다. 비 abstract클래스에서 abstract 메서드를 재정의할 때는 꼭 @Override 어노테이션을 사용할 필요는 없지만 명시적으로 알리고 싶을 때는 사용해도 무관하다.

하지만 추상 클래스나 인터페이스으 ㅣ경우에는 그 상위 클래스나 상위 인터페이스 메서드를 재정의하는 모든 메서드에, abstract 메서드건 아니건 어노테이션을 붙힐 필요가 있다.

요약

상위 자료형에 선언된 메서드를 재정의하는 모든 메서드에 Override 어노테이션을 붙이도록 하면 굉장히 많은 오류를 막을 수 있다. 예외적으로, 비-abstract 클래스에서 상위 클래스의 abstract 메서드를 재정의 할 때는 Override 어노테이션을 붙이지 않아도 된다.



도구나 프레임워크가 특별히 취급해야 하는 프로그램 요소를 구별하기 위해 작명 패턴을 사용했다. 이러한 작명 패턴에는 몇 가지 문제점이 있다.

우선, 철자를 틀리면 알아채기 힘든 문제가 생긴다. 예를 들어 테스트 메서드의 이름을 testSafetyOverride가 아니라 tsetSafetyOverride라고 했다 치자. JUnit은 아무런 불평도 하지 않을 것이다, 테스트를 실행하지도 않을 것이다.

 둘 째, 특정한 프로그램 요소에만 적용되도록 만들 수 없다.

 셋 째, 프로그램 요소에 인자를 전달할 마땅한 방법이 없다는 것이다. 예를 들어 특정 예외가 발생해야 성공으로 판정하는 테스트를 지우너하고 싶다고  해 보자. 해당 테스트에는 예외 자료형이 반드시 인자로 전달되어야 할 것이다. 


 어노테이션은 이러한 문제를 해결한다. 아래의 코드는 예외가 발생하면 실패하도록 하는 테스트 코드이다.



요약

대부분의프로그래머는, 도구 개발에 관심있는 개발자가 아니라면, 어노테이션 자료형을 정의할 필요가 없다. 그렇다 하더라도 모든 프로그래머는 자바 플랫폼이 제공하는 어노테이션 자료형들을 사용하도록 해야 한다. 


형 안전 enum 패턴을 쓸 경우에는 다른 열거 자료형을 계승해서 새로운 열거 자료형을 만드는 것이 가능하지만 enum 자료형으로는 그럴 수 없다.  그러나 이것을 단점으로 볼 수는 없는데, enum 자료형을 계승한다는 것은 대체로 바람직하지 않기 때문이다. 하지만 열거 자료형의 확장이 가능하면 좋은 경우가 있는데 연산코드를 만들어야 할 때이다.  때로는 API 사용자가 API가 기본 제공하는 연산 집합을 확장하여 자기만의 연산을 추가할 수 있도록 해야할 때가 있기 때문이다


 이러한 연산 자료형의 확장은 Enum 자료형이 임의의 이넡페이스를 구현할 수 있다는 사실을 이용하는 것이다. 


 BasicOperation은 enum자료형이라 계승할 수 없지만 Operation은 인터페이스라 확장이 가능하다. API가 사용하는 모든 연산은 이 인터페이스로 표현한다. 따라서 이 인터페이스를 계승하는 새로운 ENUM 자료형을 만들면 Operation 객체가 필요한 곳에 해당 enum 자료형의 상수를 이용할 수 있다. 

인터페이스를 사용해 확장 가능한 enum 자료형을 만드는 방법에는 한 가지 사소한 문제가 있다. enum 구현 자체는 계승할 수 없다는 것이다. 공유해야 하는 부분이 많아질 수록 중복코드가 많아지게 된다(인터페이스를 다 구현해야 하니까). 공유가 되는 기능은 도움 클래스 (helper class)나 정적 도움 메서드(static helper method)에 구현하여 중복을 최소화 해야한다.

요약

계승 가능 enum 자료형은 만들 수 없지만, 인터페이스를 만들고 그 인터페이스를 구현하는 기본 enum 자료형을 만들면 계승 가능 enum 자료형을 흉내낼 수 있다.



떄로 ordinal 메서드가 반환하는 값을 배열 첨자로 이용하는 코드를 만날 때가 있다. 



 이제 화단에 심은 허브들을 나타내는 배열이 하나 있다고 해 보자. 이 허브들을 품좀별로 나열해야 한다. 그러러면 품졸별 집합을 세 개 만든 다음에, 허브 각각을 그 품종에 맞는 집합에 넣어야 한다. 일부 프로그래머들은 배열을 사용할 테지만 배열로 구현하면 많은 문제점이 있다. 

 배열은 제네릭과 호환되지 않으므로 배열을 쓰려면 무점검 형변환이 필요하며 깔끔하게 컴파일 되지 않는다. 거디가가 배열은 첨자가 무엇을 나타내는지 모르므로 라벨을 수동으로 달아줘야 하며, eunum이 ordinal값으로 배열 원소를 참조할 때 정확한 int값이 사용되도록 해야 한다는 것이다. int enum과 같은 수준의 형 안전성을 보장하지 않으므로 위험하다.

 사실 enum 상수를 키로 사용할 목적으로 설계된 성능이 아주 우수한 Map있는데 바로 EnumMap이다.



요약

ordinal 값을 배열 첨자로 사용하는 것은 적절치 않다는 것. 대신 EnumMap을 사용하라


열거 자료형 원소들이 주로 집합에 사용될 경우, 전통적으로는 INT ENUM 패턴을 이용했다. 아래와 같이 하면 상수들을 집합에 넣을 때 비트별로 OR연산을 사용할 수 있다.

 집합을 비트 필드로 나타내면 비트 단위 산술 연산을 통해 합집합이나 교집합 등의 집합 연산도 효율적으로 할 수 있다. 하지만 위와 같은 코드는 int enum 패턴과 똑같은 단점들을 가지고 있다. 따라서 EnumSet이라는 클래스가 있는데, 이 클래스를 사용하면 특정한 enum 자료형의 값으로 구성된 집합을 효율적으로 표현할 수 있다. 


 appleyStyles 메서드에 EnumSet 객체를 전달하는 클라이언트 코드는 아래와 같다. EnumSet에는 정적 팩터리 메서드가 다양하게 준비되어 있어서 여러개중 선택해서 사용하면 된다.

요약

열거 자료형에 집합에 사용해야 할떄는 EnumSet을 사용하여 구현하도록 하자.



모든 enum에는 ordinal이라는 메서드가 있는데, enum 자료형 안에서 enum 상수의 위치를 나타내는 정수값을 반환한다. 정말 되도록 이면 쓰지마라. 아래의 예제를 보자.


 위의 코드는 유지보수 관점에서 보면 정말 안좋은 코드다. 상수 순서를 변경하는 순간 numberOfMusicians 메서드는 깨지고 만다. 게다가 이미 사용한 정수에 값에 대응되는 새로운 enum 상수를 정의하는 것은 아예 불가능하다.  enum 상수에 연계되는 값을 ordinal을 사용해 표현하지 말고, 그런 값이 필요하다면 그 대신 객체 필드에 저장해야 한다는 것.


 

열거 자료형은 고정 개수의 상수들로 값이 구성되는 자료형이다. 자바에는 enum이라는 자료형이 도입되기 전에는 아래와 같이 통상 int 형의 상수를 정의해서 열거 자료형을 흉내냈다.

 

 이 기법은 INT ENUM 패턴으로 알려져 있는데, 단점이 많다. int 형의 값으로 상수를 비교하기 때문에 int형 값이 중복되는 경우 제대로 된 비교가 되지 않는다. 또한 이러한 상수는 출력하거나 디버거로 봤을 경우 의미없는 숫자로만 보인다. 또한 int enum 깨지기 쉬운데 그 이유는 상수는 컴파일 시점 상수이기 떄문에 상수를 사용하는 클라이언트 코드와 함께 컴파일 된다. 상수의 int 값이 변경되면 클라이언트도 다시 컴파일해야 한다.


 자바의 enum 자료형 이면에 감춰진 기본적 아이디어는 단순하다. 열거 상수별로 하나의 객체를 public static final 필드 형태로 제공하는 것이다. enum 자료형으로 새로운 객체를 생성하거나 계승을 통해 확장할 수 없기 때문에, 이미 선언된 enum 상수 이외의 객체는 사용할 수 없다. 즉 enum 자료형의 개체수는 엄격이 통제된다는 것이다. 

 

 또한 enum 자료형은 컴파일 시점 형 안전성을 제공한다. Apple형의 인자를 받는다고 선언한 메서드는 반드시 Apple 값 중 하나의 값만 받을 수 있다. 엉뚱한 자료형의 값을 인자로 전달하려 하면, 자료형 불일치 때문에 컴파일 할 떄 오류가 발생한ㄷ.ㅏ 

 

 enum 자료형은 같은 이름으 ㅣ상수가 평화롭게 공존할 수 있도록 한다. 이름 공간이 분리되기 떄문이다. 상수를 추가하거나 순서를 변경해도 클라이언트는 다시 컴파일할 필요가 없다. 상수를 제공하는 필드가 enum 자료형과 클라이언트 사이에서 격리 계층 구실을 하기 때문이다. 또한 enum 자료형은 toString 메서드를 통해 인쇄 가능 문자열로 쉽게 변환할 수 있다. 


 enum 자료형은 임의의 메서드나 필드도 추가할 수 있도록 한다. 임의의 인터페이스를 구현할 수도 있다. 그렇다면 enum 자료형에 메서드나 필드를 추가하는 이유는 무엇일까? 상수에 데이터를 연관시키면 좋겠다는 생각이 들어서인데, enum 자료형은 상수 묶음에서 출발해서 점차로 완전한 기능을 갖춘 추상화 단위로 진화해 나갈 수 있다. 


 일반적으로 유용하게 쓰일 enum이라면, 최상위 클래스로 선언해야 한다. 특정한 최상위 클래스에서만 쓰이는 enum이라면 해당 클래스의 멤버 클래스로 선언해야 한다. 


요약

enum 자료형은 int 상수에 비해 많은 중요한 장점을 가지고 있다. enum을 사용한 코드는 가독성도 높고, 안전하며, 더 강력하다. 상당수의 enum은 생성자나 멤버가 필요없으나, 데이터 또는 그 데이터에 관계된 메서드를 추가해서 기능을 향상 시킬수도 있다. 또한 동일한 메서드가 상수별로 다르게 동작하도록 만들어야 하는 enum도 구현 가능하다. 


+ Recent posts