Effective Java 6장 열거형(enum)과 제네릭
열거형과 어노테이션 (30~37)
- Java 에서 열거 자료형(enum type) 은 참조 자료형이다.
ITEM30 : int 상수 대신 enum을 사용하라
- 열거 자료형(enumerated type)은 고정 개수의 상수들로 값이 구성되는 자료형
- int 대신 enum 을 사용하면 공간적 시간적 손해를 보긴핮만 성능면에서 비등하다
- enum 은 가독성이 좋고 안전하며 강력함.
-
생성자나 멤버가 필요없으나 데이터 또는 그데이터에 관계된 메서드를 추가해서 기능을 향상시킨 enum 도 많다!
- int enum 패턴 : 사용하면안된다
1
2
3
4
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN =1;
...
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 인터페이스 구현되어있다!
- C/C++/C# 언어가 제공하는 enum 이랑 다름, 다른언어에서는 단순 int 값이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public enum Planet {
MERCURY (3.302e+23, 2.439e6),
VENUS .
EARTH
MARS
JUPITER
SATURE
URANUS
NEPTUNE
private final double mass; // 킬로그램 단위
private final double radius;
private final dobule surfaceGravity;
// MERCURY (3.302e+23, 2.439e6) 처럼 두개의 값을 초기화 하는 생성자
Planet(double mass, double radius){
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
// 접근자
public double mass() { return mass; }
...
// 추가 메서드
public double surcfaceWight(double mass){
return mass * surfaceGravity; // F = ma
}
}
// 사용
double mass = earthWeight / Planet.EARTH.surfaceGravity();
// values 라는 static 함수존재한다! 이외에도 기본 제공함수들이 많음!
for(Planet p : Planet.values() )
p.surfaceWeight(mass)
- enum 상수에 데이터를 넣으려면 객체 필드를 선언하고 생성자를 통해 받은 데이터를 그 필드에 저장하면 된다!
- enum 도 클라이언트에게 공개할 이유가 없다면 private 나 package-private로 선언해야 한다.
-
공개한다면 최상위 public 클래스로 생성하거나, 최상위 클래스의 멤버 클래스로 선언하면 된다.
- switch 문 대신 상수별 메서드를 구현해야 한다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public enum Operation {
// switch 로 +,-,*,/ 일때 분기해서 처리하는것보다 이런식으로 처리하는게 확장성에 좋다.
// 새로운 ^ 과 같은 값이 추가된다면 switch 로 하면 문제없이 컴파일 되지만 abstract 방식을 사용하면 컴파일 에러 발생!
PLUS("+") {
double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
abstract double apply(double x, double y);
private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>();
static {
// Operation 생성자 안에서 stringToEnum 에 값을 넣으면 NullPointerException 이 난다.
// enum 생성자안에서는 enum 의 static 필드를 접근할수 없다. (컴파일 시점에서 상수면 접근 가능)
// 생성자가 실행될때 static 필드는 초기화된 상태가 아니기 떄문!
for (Operation op : values())
stringToEnum.put(op.toString(), op);
}
public static Operation fromString(String symbol) {
return stringToEnum.get(symbol);
}
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
- enum 값에 따라 처리하는 방법2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
* payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
// 추상 메소드! 확장성에 좋다!
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
ITEM31 : ordinal 대신 객체 필드를 사용하라
- 모든 enum 에는 ordinal() 이라는 메서드가 있음
1
2
3
4
5
6
public num Ensemble {
SOLO, DUET, TRIO, QUATET, QUINTET,..
// 절대 만들어서는 안되는 메소드, 타입을 추가하거나 제외할 경우에 매우 취약하다.
public int numberOfMusicians() { return ordinal() + 1;}
}
-
ENUM 형 값에 명시적으로 값을 줄수도 있다. 이런경우에 값을 바꿀경우 numberOfMusicians() 함수가 외부에 종속적이므로 문제가 발생할수 있다.
-
ENUM 상수에 연계되는 값을 ordinal 을 사용해 표현하지 말아라!!! 그런값이 필요하다면 그대신 객체 필드에 저장해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum Ensemble {
// 명시적으로 enum 숫자 정해줌
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(
8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
// 생성 할때 사이즈 받는다!
Ensemble(int size) {
this.numberOfMusicians = size;
}
// 객체 필드를 리턴함
public int numberOfMusicians() {
return numberOfMusicians;
}
}
- ordinal() 함수 자체는 EnumSet EnumMap 처럼 일반적인 용도의 enum 기반 자료구조에서 사용할 목적으로 설계한 메서드 이다.
- EnumSet : 비트 필드 대신에 사용해야하는 자료구조
- EnumSet.of(Ensemble.SOLO,Ensemble,DUET) _ EnumMap : 맵에 키값으로 enum 필드를 사용할수 있는 자료구조
- EnumMap<Ensemble, String> map = new ..
- EnumSet : 비트 필드 대신에 사용해야하는 자료구조
ITEM32 : 비트 필드(bit field) 대신 EnumSet을 사용하라
- 열거 자료형 원소들을 집합에 사용할때 C 스타일로 int eunm 패턴에 2의 거듭제곱 값을 대입해서 사용했다
1
2
3
4
5
6
7
public class Text{
public static final int STYLE_BOLD = 1 << 0; //1
public static final int STYLE_ITALIC = 1 << 1; // 2
...
public void applyStyle(int styles){ ..}
}
text.appleStyles(STYLE_BOLD | STYLE_ITALIC); // 원소들을 집합으로 사용하기 위해서 | 비트 연산을 이용
- 집합을 비트 필드로 나타내면 비트 단위 산술 연산을 통해 합집합 교집합 등의 집합 연산도 효울적으로 실행할수 있다. 하지만 여러 단점이 존재함!!
- 상수를 출력해도 이해하기가 어렵다.
- 비트 필드에 포함된 모든 요소를 순차적으로 살펴보기도 어렵다.
- 대신에 java.util.EnumSet 을 사용하면 된다.
- Set 인터페이스을 구현해서 Set 이 제공하는 풍부한 기능을 그대로 사용할수 있다.
- 형 안정성, 상호운영성(다른 구조의 Set도 EnumSet으로 바꿀수 있다.) 을 제공함!
- EnumSet 내부 구현 자체는 bit vector 로 되어있다. enum 값 갯수가 64개 이하이면 long 값 하나만 사용할수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Text {
public enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
// EnumSet 이 아니라 Set 으로 사용가능하게 했다.
// 인터페이스를 자료형으로 쓰므로 인해서 상호 운영성을 얻을수 있다.
public void applyStyles(Set<Style> styles) {
// Body goes here
}
public static void main(String[] args) {
Text text = new Text();
// EnumSet 사용!
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
}
}
- 열거 자료형을 집합에 사용해야 한다고 해서 비트 필드로 구현하지마라!!!
- 자바1.6 기준으로 EnumSet 은 변경 불가능한 객체를 만들수 없다.
- 이후 변경될것이다.
- Java 1.7 에서도 불가능하다. Guava 를 사용하면 가능하다
ITEM33 : ordinal을 배열 첨자로 사용하는 대신 EnumMap을 이용하라
- ordinal 을 사용해 여러 자바 자료구조와 Enum 형을 맵핑 시키는 일은 하면 안된다.
- 형 안정성을 제공하지 못하고, 틀린값을 쓰게 되면 이상한 짓을 하게 되거나 해당 Index 를 넘어갈경우 ArrayIndexOutOfBoundsExeception 을 만들수도 있다.
- java.util.EnumMap 을 사용하여 enum 상수를 키로 사용하는 맵을 만들자!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Herb {
public enum Type {
ANNUAL, PERENNIAL, BIENNIAL
}
private final String name;
private final Type type;
Herb(String name, Type type) {
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
public static void main(String[] args) {
Herb[] garden = { new Herb("Basil", Type.ANNUAL),
new Herb("Carroway", Type.BIENNIAL),
new Herb("Dill", Type.ANNUAL),
new Herb("Lavendar", Type.PERENNIAL),
new Herb("Parsley", Type.BIENNIAL),
new Herb("Rosemary", Type.PERENNIAL) };
// Enum 형 Herb.Type 을 키로 사용!!
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
Herb.Type.class);
for (Herb.Type t : Herb.Type.values())
herbsByType.put(t, new HashSet<Herb>());
for (Herb h : garden)
herbsByType.get(h.type).add(h);
System.out.println(herbsByType);
}
}
- 내부적으로는 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(
GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
private final Phase src;
private final Phase dst;
Transition(Phase src, Phase dst) {
this.src = src;
this.dst = dst;
}
// 변경 테이블을 맵으로 만든다.
private static final Map<Phase, Map<Phase, Transition>> m = new EnumMap<Phase, Map<Phase, Transition>>(
Phase.class);
static {
// 가능한 변화를 초기화
for (Phase p : Phase.values())
m.put(p, new EnumMap<Phase, Transition>(Phase.class));
for (Transition trans : Transition.values())
m.get(trans.src).put(trans.dst, trans);
}
public static Transition from(Phase src, Phase dst) {
return m.get(src).get(dst);
}
}
public static void main(String[] args) {
for (Phase src : Phase.values())
for (Phase dst : Phase.values())
if (src != dst)
System.out.printf("%s to %s : %s %n", src, dst,
Transition.from(src, dst));
}
}
ITEM34 : 확장 가능한 enum을 만들어야 한다면 인터페이스를 이용하라
- ENUM 형은 기본적으로 계승이 안된다!!
- 계승이 되는거처럼 흉내 내려면 인터페이스를 만들어서 같은 인터페이스를 implements 할수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// BasicOperation 처럼 Operation 구현
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(ExtendedOperation.class, x, y);
System.out.println();
test2(Arrays.asList(ExtendedOperation.values()), x, y);
}
// Enum 형을 테스트 하기 위해 한정적 자료형 토큰을 넘기는 방법
// Class 에 getEnumConstants 를 사용해 가능한 Operation 을 가져올수 있다.
private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
double x, double y) {
for (Operation op : opSet.getEnumConstants())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
// Enum 형을 테스트 하기 위해 한정적 와일드 카드 자료형을 사용
private static void test2(Collection<? extends Operation> opSet, double x,
double y) {
for (Operation op : opSet)
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
- _<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
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 어노테이션 사용
// 메타 주석
@Retention(RetentionPolicy.RUNTIME) // Test 주석이 런타임시에 존속되어야 한다
@Target(ElementType.METHOD) // 메소드 선언시에만 적합하다.
public @interface Test {
}
-
메타 주석 : 주석 타입 선언에 나온 주석
-
특정 예외를 발생 시키는 경우에 한해서 테스트가 성공하도록 만들기
1
2
3
4
5
6
7
8
9
10
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Exception> value(); // 한개의 타입만 받는다.
Class<? extends Exception>[] value(); // 여러개의 타입을 받는다.
/*
Class<? extends Exception>[] excTypes = m.getAnnotation(
ExceptionTest.class).value(); // 처럼 사용
*/
}
- Class<? extends Exception>
- Exception 예외 클래스에서 상속받은 어떤 클래스의 class 객체
- 이주석 사용자가 어떤 예외 타입을 지정해도 된다!(바운드 타입 토큰)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Sample2 {
// 매개변수로 클래스 리터럴을 받는 주석!
@ExceptionTest(ArithmeticException.class)
public static void m1() {
// 예외 발생하므로 성공하는 테스트
int i = 0;
i = i / i;
}
@ExceptionTest(ArithmeticException.class)
public static void m2() {
// 잘못된 Exception 으로 실패하는 테스트
int[] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmeticException.class)
public static void m3() {
} // 예외가 발생 안하므로 실패하는 테스트
@ExceptionTest({ IndexOutOfBoundsException.class,
NullPointerException.class })
public static void doublyBad() {
List<String> list = new ArrayList<String>();
// IndexOutOfBoundsException or NullPointerException 에러 발생
list.addAll(5, null);
}
}
- Test Annotation 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class RunTests {
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
// Test Annotation 이 적용된것만 테스트!
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " failed: " + exc);
} catch (Exception exc) {
System.out.println("INVALID @Test: " + m);
}
}
// 만약에 Exception Annotation 이라면
if (m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (Throwable wrappedExc) {
Throwable exc = wrappedExc.getCause();
// 가능한 예외 목록을 처리
Class<? extends Exception>[] excTypes = m.getAnnotation(
ExceptionTest.class).value();
int oldPassed = passed;
// 여러개의 타입을 받는것을 처리
for (Class<? extends Exception> excType : excTypes) {
if (excType.isInstance(exc)) {
passed++;
break;
}
}
if (passed == oldPassed)
System.out.printf("Test %s failed: %s %n", m, exc);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
}
ITEM36 : 어노테이션은 일관되게 사용하라
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> s = new HashSet<Bigram>();
for (int i = 0; i < 10; i++)
for (char ch = 'a'; ch <= 'z'; ch++)
s.add(new Bigram(ch, ch));
System.out.println(s.size());
}
}
- 26 개의 바이그램을 10번 반복해서 Set 에 추가함! 정상적이라면 26 을 출력해야 한다! 하지만 모든 오브젝트가 다르게 인식되어서 260개가 출력됨
- Bigram 클래스 개발자는 equals 를 오버라이딩을 하려 했다 하지만 eqauls(Object b) 가 되어야 하는데 잘못 오버라이딩 해주었다.
- @Override 주석을 eauls 함수에 넣어주면 컴파일 에러가 발생해서 실수를 방지할수 있다.
ITEM37 : 자료형을 정의할 때 표식 인터페이스를 사용하라
- 표시 인터페이스(marker interface)는 메소드 선언은 전혀없으면서 클래스가 그 인터페이스를 구현하는지만 나타내는(표시하는) 인터페이스이다.
- ex) Serializable
- 표시 인터페이스는 주석에 비해 2가지 장점이 있다.
- 표시된 클래스의 인스턴스에 의해 구현되는 타입을 정의한다. 타입이 있기때문에 주석이 런타입에서 에러를 잡을수 있을것을 컴파일 시점에서 할수 있다.
- Serialzable 의 ObjectOutputStream.write(Object) 함수는 Serialzable 을 메소드 인자 타입으로 이용하지 않고 Object 를 이용했다. 따라서 컴파일 시점이 아닌 런타임에서 오류가 나도록 잘못 설계가 되어있다.
- 더 정확한 목표를 가실수 있다는 장점이있다. 주석타입이 어떤 목표로 선언된다면 그 주석은 어떤 클래스나 인터페이스에도 적용할수 있게 된다. 표시 인터페이스를 정의하면 그것을 적용 가능한 인터페이스로 확장할수 있고 모든 표시 타입들도 그적용 가능한 인터페이스의 서브 타입이 되도록 할수 있다.
- 표시된 클래스의 인스턴스에 의해 구현되는 타입을 정의한다. 타입이 있기때문에 주석이 런타입에서 에러를 잡을수 있을것을 컴파일 시점에서 할수 있다.
- Set 인터페이스는 제한적 표시 인터페이스 이다. ___
- 주석이 표시 인터페이스에 비해 갖는 장점
- 하나 이상의 주석 타입 요소들을 추가함으로써 더 많은 정보를 주석타입에 추가할수 있다!
- 클래스나 인터페이스가 아닌 다른 포로그램 요소에 적용할 때는 주석을 사용해야 한다.
-
클래스와 인터페이스에만 적용하되 표시자를 갖는 개체만을 인자로 받는 메소드를 하나 이상작성하지 않는다면 그표시자를 영원히 특정 인터페이스의 요소에만 제한해서 사용할 것인지 고려해 봐야한다. 그렇다면 인터페이스의 서브 인터페이스로 표시인터페이스를 정의하는 것이 좋다
- 정의할 타입과 연관된 어떤 새로운 메소드도 갖지 않는 타입을 정의하고자 한다면 표시 인터페이스
- 클래스와 인터페이스가 아닌 다른 프로그램 요소를 표시한다면, 향후에 더많은 정볼르 표시자에 추가할 가능성이 있다면 표시 주석!