열거형과 어노테이션 (30~37)

  • Java 에서 열거 자료형(enum type) 은 참조 자료형이다.

ITEM30 : int 상수 대신 enum을 사용하라

  • 열거 자료형(enumerated type)은 고정 개수의 상수들로 값이 구성되는 자료형
  • int 대신 enum 을 사용하면 공간적 시간적 손해를 보긴핮만 성능면에서 비등하다
  • enum 은 가독성이 좋고 안전하며 강력함.
  • 생성자나 멤버가 필요없으나 데이터 또는 그데이터에 관계된 메서드를 추가해서 기능을 향상시킨 enum 도 많다!

  • int enum 패턴 : 사용하면안된다
1 public static final int APPLE_FUJI = 0;
2 public static final int APPLE_PIPPIN =1;
3 ...
4 int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN; // 변수가 섞임
  • 단점
    • 상수를 사용하는 클라이언트 코드와 함께 컴파일됨, 값이 변경되면 다시 컴파일 다시해야함
    • 크기를 알아낼수 없음
    • 디버깅이 불편함
      • String enum 패턴으로 해결 가능하지만 더더욱 최악의방법
1 public enum Apple { FUJI, PIPPIN, GRANNY_SMITH}
  • enum
    • C/C++/C# 언어가 제공하는 enum 이랑 다름, 다른언어에서는 단순 int 값이다
      • Java 에서는 참조 자료형
    • enum 자료형은 public static final 필드 형태로 제공하는것
    • 클라이언트가 접근할수 있는 생성자가 없다. 계승 확장 불가능
    • 컴파일 시점에 형 안정성 제공한다! 같은 enum형 에서 비교연산자 == 가 가능하다!
    • 같은 이름의 상수가 공존 가능.
    • enum 형은 toString 메서드를 호출 가능해서 문자열로 쉽게 변경 가능!
    • Object 에 정의된 메서드들이 포함되어있다.
    • Comparable, Serializable 인터페이스 구현되어있다!
 1 public enum Planet {
 2      MERCURY (3.302e+23, 2.439e6),
 3      VENUS .
 4      EARTH
 5      MARS
 6      JUPITER
 7      SATURE
 8      URANUS
 9      NEPTUNE
10     
11      private final double mass; // 킬로그램 단위
12      private final double radius;
13      private final dobule surfaceGravity;
14     
15      // MERCURY (3.302e+23, 2.439e6) 처럼 두개의 값을 초기화 하는 생성자
16      Planet(double mass, double radius){
17           this.mass = mass;
18           this.radius = radius;
19           surfaceGravity = G * mass / (radius * radius);
20      }
21     
22      // 접근자
23      public double mass() { return mass; }
24      ...
25     
26      // 추가 메서드
27      public double surcfaceWight(double mass){
28           return mass * surfaceGravity; // F = ma
29      }
30 }
31 
32 // 사용
33 double mass = earthWeight / Planet.EARTH.surfaceGravity();
34 // values 라는 static 함수존재한다! 이외에도 기본 제공함수들이 많음!
35 for(Planet p : Planet.values() )
36      p.surfaceWeight(mass)
  • enum 상수에 데이터를 넣으려면 객체 필드를 선언하고 생성자를 통해 받은 데이터를 그 필드에 저장하면 된다!
  • enum 도 클라이언트에게 공개할 이유가 없다면 private 나 package-private로 선언해야 한다.
  • 공개한다면 최상위 public 클래스로 생성하거나, 최상위 클래스의 멤버 클래스로 선언하면 된다.

  • switch 문 대신 상수별 메서드를 구현해야 한다!
 1 public enum Operation {
 2      // switch 로 +,-,*,/ 일때 분기해서 처리하는것보다 이런식으로 처리하는게 확장성에 좋다.
 3      // 새로운 ^ 과 같은 값이 추가된다면 switch 로 하면 문제없이 컴파일 되지만 abstract 방식을 사용하면 컴파일 에러 발생!
 4      PLUS("+") {
 5           double apply(double x, double y) {
 6                return x + y;
 7           }
 8      },
 9      MINUS("-") {
10           double apply(double x, double y) {
11                return x - y;
12           }
13      },
14      TIMES("*") {
15           double apply(double x, double y) {
16                return x * y;
17           }
18      },
19      DIVIDE("/") {
20           double apply(double x, double y) {
21                return x / y;
22           }
23      };
24      private final String symbol;
25 
26      Operation(String symbol) {
27           this.symbol = symbol;
28      }
29 
30      @Override
31      public String toString() {
32           return symbol;
33      }
34 
35      abstract double apply(double x, double y);
36 
37      private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>();
38      static {
39           // Operation 생성자 안에서 stringToEnum 에 값을 넣으면 NullPointerException 이 난다.
40           // enum 생성자안에서는 enum 의 static 필드를 접근할수 없다. (컴파일 시점에서 상수면 접근 가능)
41           // 생성자가 실행될때 static 필드는 초기화된 상태가 아니기 떄문!
42           for (Operation op : values())
43                stringToEnum.put(op.toString(), op);
44      }
45 
46      public static Operation fromString(String symbol) {
47           return stringToEnum.get(symbol);
48      }
49 
50      public static void main(String[] args) {
51           double x = Double.parseDouble(args[0]);
52           double y = Double.parseDouble(args[1]);
53           for (Operation op : Operation.values())
54                System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
55      }
56 }
  • enum 값에 따라 처리하는 방법2
 1 enum PayrollDay {
 2      MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
 3                PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
 4                PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
 5 
 6      private final PayType payType;
 7 
 8      PayrollDay(PayType payType) {
 9           this.payType = payType;
10      }
11 
12      double pay(double hoursWorked, double payRate) {
13           return payType.pay(hoursWorked, payRate);
14      }
15 
16      private enum PayType {
17           WEEKDAY {
18                double overtimePay(double hours, double payRate) {
19                     return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
20                               * payRate / 2;
21                }
22           },
23           WEEKEND {
24                double overtimePay(double hours, double payRate) {
25                     return hours * payRate / 2;
26                }
27           };
28           private static final int HOURS_PER_SHIFT = 8;
29          
30           // 추상 메소드! 확장성에 좋다!
31           abstract double overtimePay(double hrs, double payRate);
32 
33           double pay(double hoursWorked, double payRate) {
34                double basePay = hoursWorked * payRate;
35                return basePay + overtimePay(hoursWorked, payRate);
36           }
37      }
38 }

ITEM31 : ordinal 대신 객체 필드를 사용하라

  • 모든 enum 에는 ordinal() 이라는 메서드가 있음
1 public num Ensemble {
2     SOLO, DUET, TRIO, QUATET, QUINTET,..
3     
4     // 절대 만들어서는 안되는 메소드, 타입을 추가하거나 제외할 경우에 매우 취약하다.
5     public int numberOfMusicians() { return ordinal() + 1;} 
6 }
  • ENUM 형 값에 명시적으로 값을 줄수도 있다. 이런경우에 값을 바꿀경우 numberOfMusicians() 함수가 외부에 종속적이므로 문제가 발생할수 있다.

  • ENUM 상수에 연계되는 값을 ordinal 을 사용해 표현하지 말아라!!! 그런값이 필요하다면 그대신 객체 필드에 저장해야 한다.

 1 public enum Ensemble {
 2     // 명시적으로 enum 숫자 정해줌
 3     SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(
 4             8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);
 5 
 6     private final int numberOfMusicians;
 7     
 8     // 생성 할때 사이즈 받는다!
 9     Ensemble(int size) {
10         this.numberOfMusicians = size;
11     }
12     // 객체 필드를 리턴함
13     public int numberOfMusicians() {
14         return numberOfMusicians;
15     }
16 }
  • ordinal() 함수 자체는 EnumSet EnumMap 처럼 일반적인 용도의 enum 기반 자료구조에서 사용할 목적으로 설계한 메서드 이다.
    • EnumSet : 비트 필드 대신에 사용해야하는 자료구조
      • EnumSet.of(Ensemble.SOLO,Ensemble,DUET) _ EnumMap : 맵에 키값으로 enum 필드를 사용할수 있는 자료구조
      • EnumMap<Ensemble, String> map = new ..

ITEM32 : 비트 필드(bit field) 대신 EnumSet을 사용하라

  • 열거 자료형 원소들을 집합에 사용할때 C 스타일로 int eunm 패턴에 2의 거듭제곱 값을 대입해서 사용했다
1 public class Text{
2     public static final int STYLE_BOLD = 1 << 0; //1
3     public static final int STYLE_ITALIC = 1 << 1; // 2
4     ...
5     public void applyStyle(int styles){ ..}
6 }
7 text.appleStyles(STYLE_BOLD | STYLE_ITALIC); // 원소들을 집합으로 사용하기 위해서 | 비트 연산을 이용
  • 집합을 비트 필드로 나타내면 비트 단위 산술 연산을 통해 합집합 교집합 등의 집합 연산도 효울적으로 실행할수 있다. 하지만 여러 단점이 존재함!!
    • 상수를 출력해도 이해하기가 어렵다.
    • 비트 필드에 포함된 모든 요소를 순차적으로 살펴보기도 어렵다.
  • 대신에 java.util.EnumSet 을 사용하면 된다.
    • Set 인터페이스을 구현해서 Set 이 제공하는 풍부한 기능을 그대로 사용할수 있다.
    • 형 안정성, 상호운영성(다른 구조의 Set도 EnumSet으로 바꿀수 있다.) 을 제공함!
    • EnumSet 내부 구현 자체는 bit vector 로 되어있다. enum 값 갯수가 64개 이하이면 long 값 하나만 사용할수 있다.
 1 public class Text {
 2     public enum Style {
 3         BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
 4     }
 5     // EnumSet 이 아니라 Set 으로 사용가능하게 했다. 
 6     // 인터페이스를 자료형으로 쓰므로 인해서 상호 운영성을 얻을수 있다.
 7     public void applyStyles(Set<Style> styles) {
 8         // Body goes here
 9     }
10 
11     public static void main(String[] args) {
12         Text text = new Text();
13         // EnumSet 사용!
14         text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
15     }
16 }
  • 열거 자료형을 집합에 사용해야 한다고 해서 비트 필드로 구현하지마라!!!
  • 자바1.6 기준으로 EnumSet 은 변경 불가능한 객체를 만들수 없다.
    • 이후 변경될것이다.
    • Java 1.7 에서도 불가능하다. Guava 를 사용하면 가능하다

ITEM33 : ordinal을 배열 첨자로 사용하는 대신 EnumMap을 이용하라

  • ordinal 을 사용해 여러 자바 자료구조와 Enum 형을 맵핑 시키는 일은 하면 안된다.
    • 형 안정성을 제공하지 못하고, 틀린값을 쓰게 되면 이상한 짓을 하게 되거나 해당 Index 를 넘어갈경우 ArrayIndexOutOfBoundsExeception 을 만들수도 있다.
  • java.util.EnumMap 을 사용하여 enum 상수를 키로 사용하는 맵을 만들자!
 1 public class Herb {
 2     public enum Type {
 3         ANNUAL, PERENNIAL, BIENNIAL
 4     }
 5 
 6     private final String name;
 7     private final Type type;
 8 
 9     Herb(String name, Type type) {
10         this.name = name;
11         this.type = type;
12     }
13 
14     @Override
15     public String toString() {
16         return name;
17     }
18 
19     public static void main(String[] args) {
20         Herb[] garden = { new Herb("Basil", Type.ANNUAL),
21                 new Herb("Carroway", Type.BIENNIAL),
22                 new Herb("Dill", Type.ANNUAL),
23                 new Herb("Lavendar", Type.PERENNIAL),
24                 new Herb("Parsley", Type.BIENNIAL),
25                 new Herb("Rosemary", Type.PERENNIAL) };
26 
27         // Enum 형 Herb.Type 을 키로 사용!!
28         Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
29                 Herb.Type.class);
30         for (Herb.Type t : Herb.Type.values())
31             herbsByType.put(t, new HashSet<Herb>());
32         for (Herb h : garden)
33             herbsByType.get(h.type).add(h);
34         System.out.println(herbsByType);
35     }
36 }
  • 내부적으로는 ordinal 을 사용한다. 명시적으로 ordinal 을 사용하는것과 성능상에 크게 차이가 없다.
  • new EnumMap<Herb.Type, Set<Herb>>( Herb.Type.class)
    • 생성자에 키의 자료형을 나타내는 Class 객체를 인자로 받는다!.
    • Class 객체를 한정적 자료형 토큰으로 사용해서 실행 시점에서 제네릭 자료형 정보를 제공한다.
      • EnumMap 은 내부적으로 new Object[length] 형식으로 되어있다. 타입을 체크하고 리턴하기 위해서는 타입정보를 알아야한다.
      • EnumSet 은 static factory 로 생성되서 타입 정보를 넘기지 않는거 같지만 내부적으로 e.getDeclaringClass() 를 가져와서 타입정보를 가져온다. 처음 생성시에 타입을 넘기므로 EnumMap 처럼 생성할때 타입정보를 넘기지 않아도 된다.
  • 액체 고체 기체 변화를 나타내는 Enum 형
    • 표현해야 하는 관계가 다차원적이라면 EnumMap<… , EnumMap<…>> 과 같이 사용하면 된다.
 1 public enum Phase {
 2     SOLID, LIQUID, GAS;
 3 
 4     public enum Transition {
 5         MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(
 6                 GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
 7 
 8         private final Phase src;
 9         private final Phase dst;
10 
11         Transition(Phase src, Phase dst) {
12             this.src = src;
13             this.dst = dst;
14         }
15 
16         // 변경 테이블을 맵으로 만든다.
17         private static final Map<Phase, Map<Phase, Transition>> m = new EnumMap<Phase, Map<Phase, Transition>>(
18                 Phase.class);
19         static {
20             // 가능한 변화를 초기화
21             for (Phase p : Phase.values())
22                 m.put(p, new EnumMap<Phase, Transition>(Phase.class));
23             for (Transition trans : Transition.values())
24                 m.get(trans.src).put(trans.dst, trans);
25         }
26 
27         public static Transition from(Phase src, Phase dst) {
28             return m.get(src).get(dst);
29         }
30     }
31 
32     public static void main(String[] args) {
33         for (Phase src : Phase.values())
34             for (Phase dst : Phase.values())
35                 if (src != dst)
36                     System.out.printf("%s to %s : %s %n", src, dst,
37                             Transition.from(src, dst));
38     }
39 }

ITEM34 : 확장 가능한 enum을 만들어야 한다면 인터페이스를 이용하라

  • ENUM 형은 기본적으로 계승이 안된다!!
    • 계승이 되는거처럼 흉내 내려면 인터페이스를 만들어서 같은 인터페이스를 implements 할수 있다.
 1 public interface Operation {
 2     double apply(double x, double y);
 3 }
 4 public enum BasicOperation implements Operation {
 5     PLUS("+") {
 6         public double apply(double x, double y) {
 7             return x + y;
 8         }
 9     },
10     MINUS("-") {
11         public double apply(double x, double y) {
12             return x - y;
13         }
14     },
15     TIMES("*") {
16         public double apply(double x, double y) {
17             return x * y;
18         }
19     },
20     DIVIDE("/") {
21         public double apply(double x, double y) {
22             return x / y;
23         }
24     };
25     private final String symbol;
26 
27     BasicOperation(String symbol) {
28         this.symbol = symbol;
29     }
30 
31     @Override
32     public String toString() {
33         return symbol;
34     }
35 }
 1 // BasicOperation 처럼 Operation 구현
 2 public enum ExtendedOperation implements Operation {
 3     EXP("^") {
 4         public double apply(double x, double y) {
 5             return Math.pow(x, y);
 6         }
 7     },
 8     REMAINDER("%") {
 9         public double apply(double x, double y) {
10             return x % y;
11         }
12     };
13 
14     private final String symbol;
15 
16     ExtendedOperation(String symbol) {
17         this.symbol = symbol;
18     }
19 
20     @Override
21     public String toString() {
22         return symbol;
23     }
24 
25     public static void main(String[] args) {
26         double x = Double.parseDouble(args[0]);
27         double y = Double.parseDouble(args[1]);
28         test(ExtendedOperation.class, x, y);
29 
30         System.out.println(); 
31         test2(Arrays.asList(ExtendedOperation.values()), x, y);
32     }
33 
34     // Enum 형을 테스트 하기 위해 한정적 자료형 토큰을 넘기는 방법 
35     // Class 에 getEnumConstants 를 사용해 가능한 Operation 을 가져올수 있다.
36     private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
37             double x, double y) {
38         for (Operation op : opSet.getEnumConstants())
39             System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
40     }
41 
42     // Enum 형을 테스트 하기 위해  한정적 와일드 카드 자료형을 사용
43     private static void test2(Collection<? extends Operation> opSet, double x,
44             double y) {
45         for (Operation op : opSet)
46             System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
47     }
48 }
  • _<T extends Enum & Operation> void test(Class opSet, double x, double y)_
    • 자료형의 class 리터럴을 사용, class 리터럴은 한정적 자료형 토큰 역활을 한다.
    • <T extends Enum<T> & Operation> : Class 객체가 나타내는 자료형이 enum 자료형인 동시에 Opertion 하위 자료형이 되어야한다. (Enum 자체는 상속이 안되고 같은 interface 를 상속하는 방식이므로 이런 한정적 방법이 필요함)
  • void test2(Collection<? extends Operation> opSet, double x, double y)
    • EnumSet 이나 EnumMap 을 사용할수 있는 유연성이 가능하다.
    • 하지만 한정적 와일드 카드 자료형 특성상 특정 operation을 직접 수행할수는 없다., 실제로 연산을 수행할수 있으르면 한정적 자료형 토큰으로 사용하자!

ITEM35 : 작명 패턴 대신 어노테이션을 사용하라

  • 자바 1.5 이전에는(어노테이션이 없을때) 도구나 프레임워크가 특별히 취급해야 되면 작명패턴을 사용했다
    • JUnit 은 테스트 이름을 test 로 시작하게 했다.
    • 이름을 잘못 입력하면 문제가 발생하거나 무시해서 문제가 생김..
    • 프로그램 요소에 인자를 전달할 방법이없다.
  • 주석 타입 정의
 1 import java.lang.annotation.ElementType;
 2 import java.lang.annotation.Retention;
 3 import java.lang.annotation.RetentionPolicy;
 4 import java.lang.annotation.Target;
 5 
 6 // 어노테이션 사용
 7 // 메타 주석
 8 @Retention(RetentionPolicy.RUNTIME) // Test 주석이 런타임시에 존속되어야 한다
 9 @Target(ElementType.METHOD) // 메소드 선언시에만 적합하다.
10 public @interface Test {
11 }
  • 메타 주석 : 주석 타입 선언에 나온 주석

  • 특정 예외를 발생 시키는 경우에 한해서 테스트가 성공하도록 만들기

 1 @Retention(RetentionPolicy.RUNTIME)
 2 @Target(ElementType.METHOD) 
 3 public @interface ExceptionTest {
 4     Class<? extends Exception> value(); // 한개의 타입만 받는다.
 5     Class<? extends Exception>[] value(); // 여러개의 타입을 받는다.
 6     /*
 7     Class<? extends Exception>[] excTypes = m.getAnnotation(
 8                             ExceptionTest.class).value(); // 처럼 사용
 9     */
10 }
  • Class<? extends Exception>
    • Exception 예외 클래스에서 상속받은 어떤 클래스의 class 객체
    • 이주석 사용자가 어떤 예외 타입을 지정해도 된다!(바운드 타입 토큰)
 1 public class Sample2 {
 2     // 매개변수로 클래스 리터럴을 받는 주석!
 3     @ExceptionTest(ArithmeticException.class)
 4     public static void m1() {
 5         // 예외 발생하므로 성공하는 테스트
 6         int i = 0;
 7         i = i / i;
 8     }
 9 
10     @ExceptionTest(ArithmeticException.class)
11     public static void m2() { 
12         // 잘못된 Exception 으로 실패하는 테스트
13         int[] a = new int[0];
14         int i = a[1];
15     }
16 
17     @ExceptionTest(ArithmeticException.class)
18     public static void m3() {
19     } // 예외가 발생 안하므로 실패하는 테스트
20 
21     @ExceptionTest({ IndexOutOfBoundsException.class,
22             NullPointerException.class })
23     public static void doublyBad() {
24         List<String> list = new ArrayList<String>();
25 
26         // IndexOutOfBoundsException or NullPointerException 에러 발생
27         list.addAll(5, null);
28     }
29 }
  • Test Annotation 처리
 1 public class RunTests {
 2     public static void main(String[] args) throws Exception {
 3         int tests = 0;
 4         int passed = 0;
 5         Class testClass = Class.forName(args[0]);
 6         for (Method m : testClass.getDeclaredMethods()) {
 7             // Test Annotation 이 적용된것만 테스트!
 8             if (m.isAnnotationPresent(Test.class)) {
 9                 tests++;
10                 try {
11                     m.invoke(null);
12                     passed++;
13                 } catch (InvocationTargetException wrappedExc) {
14                     Throwable exc = wrappedExc.getCause();
15                     System.out.println(m + " failed: " + exc);
16                 } catch (Exception exc) {
17                     System.out.println("INVALID @Test: " + m);
18                 }
19             }
20             
21             // 만약에 Exception Annotation 이라면
22             if (m.isAnnotationPresent(ExceptionTest.class)) {
23                 tests++;
24                 try {
25                     m.invoke(null);
26                     System.out.printf("Test %s failed: no exception%n", m);
27                 } catch (Throwable wrappedExc) {
28                     Throwable exc = wrappedExc.getCause();
29                     
30                     // 가능한 예외 목록을 처리
31                     Class<? extends Exception>[] excTypes = m.getAnnotation(
32                             ExceptionTest.class).value();
33                     int oldPassed = passed;
34                     // 여러개의 타입을 받는것을 처리
35                     for (Class<? extends Exception> excType : excTypes) {
36                         if (excType.isInstance(exc)) {
37                             passed++;
38                             break;
39                         }
40                     }
41                     if (passed == oldPassed)
42                         System.out.printf("Test %s failed: %s %n", m, exc);
43                 }
44             }
45         }
46         System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
47     }
48 }

ITEM36 : 어노테이션은 일관되게 사용하라

 1 public class Bigram {
 2     private final char first;
 3     private final char second;
 4 
 5     public Bigram(char first, char second) {
 6         this.first = first;
 7         this.second = second;
 8     }
 9 
10     public boolean equals(Bigram b) {
11         return b.first == first && b.second == second;
12     }
13 
14     public int hashCode() {
15         return 31 * first + second;
16     }
17 
18     public static void main(String[] args) {
19         Set<Bigram> s = new HashSet<Bigram>();
20         for (int i = 0; i < 10; i++)
21             for (char ch = 'a'; ch <= 'z'; ch++)
22                 s.add(new Bigram(ch, ch));
23         System.out.println(s.size());
24     }
25 }
  • 26 개의 바이그램을 10번 반복해서 Set 에 추가함! 정상적이라면 26 을 출력해야 한다! 하지만 모든 오브젝트가 다르게 인식되어서 260개가 출력됨
    • Bigram 클래스 개발자는 equals 를 오버라이딩을 하려 했다 하지만 eqauls(Object b) 가 되어야 하는데 잘못 오버라이딩 해주었다.
    • @Override 주석을 eauls 함수에 넣어주면 컴파일 에러가 발생해서 실수를 방지할수 있다.

ITEM37 : 자료형을 정의할 때 표식 인터페이스를 사용하라

  • 표시 인터페이스(marker interface)는 메소드 선언은 전혀없으면서 클래스가 그 인터페이스를 구현하는지만 나타내는(표시하는) 인터페이스이다.
    • ex) Serializable
  • 표시 인터페이스는 주석에 비해 2가지 장점이 있다.
    • 표시된 클래스의 인스턴스에 의해 구현되는 타입을 정의한다. 타입이 있기때문에 주석이 런타입에서 에러를 잡을수 있을것을 컴파일 시점에서 할수 있다.
      • SerialzableObjectOutputStream.write(Object) 함수는 Serialzable 을 메소드 인자 타입으로 이용하지 않고 Object 를 이용했다. 따라서 컴파일 시점이 아닌 런타임에서 오류가 나도록 잘못 설계가 되어있다.
    • 더 정확한 목표를 가실수 있다는 장점이있다. 주석타입이 어떤 목표로 선언된다면 그 주석은 어떤 클래스나 인터페이스에도 적용할수 있게 된다. 표시 인터페이스를 정의하면 그것을 적용 가능한 인터페이스로 확장할수 있고 모든 표시 타입들도 그적용 가능한 인터페이스의 서브 타입이 되도록 할수 있다.
  • Set 인터페이스는 제한적 표시 인터페이스 이다. ___
  • 주석이 표시 인터페이스에 비해 갖는 장점
    • 하나 이상의 주석 타입 요소들을 추가함으로써 더 많은 정보를 주석타입에 추가할수 있다!
  • 클래스나 인터페이스가 아닌 다른 포로그램 요소에 적용할 때는 주석을 사용해야 한다.
  • 클래스와 인터페이스에만 적용하되 표시자를 갖는 개체만을 인자로 받는 메소드를 하나 이상작성하지 않는다면 그표시자를 영원히 특정 인터페이스의 요소에만 제한해서 사용할 것인지 고려해 봐야한다. 그렇다면 인터페이스의 서브 인터페이스로 표시인터페이스를 정의하는 것이 좋다

  • 정의할 타입과 연관된 어떤 새로운 메소드도 갖지 않는 타입을 정의하고자 한다면 표시 인터페이스
  • 클래스와 인터페이스가 아닌 다른 프로그램 요소를 표시한다면, 향후에 더많은 정볼르 표시자에 추가할 가능성이 있다면 표시 주석!