Effective Java 2/E - Chatper 05 제네릭 (1)
간단/명료 하게 작성하도록 하자.
Rule 23 - 새 코드에는 무인자 제네릭 자료형을 사용하지 마라
타입 선언부에 형인자(Type parameter)가 포함되어 있으면 제네릭 타입이라 부른다.
마찬가지로 메소드 선언부에 Type parameter이 포함되어 있으면 제네릭 메서드라 부른다.
용어정리
- Type parameter : 형인자 -
< >
- Formal type parameter : 형식 형인자 -
<T>
- Actual type parameter : 실 형인자 -
<String>
- Formal type parameter : 형식 형인자 -
- Parameterized type : 형인자 자료형 -
List<T>
- Raw type : 무인자 자료형 -
List
- Unbounded wildcard type : 비한정적 와일드카드 자료형 -
List<?>
- Bounded wildcard type : 한정적 와일드카드 자료형 -
List<? extends String>
- Bound type parameter : 한정적 형인자 -
<E extends String>
목적
형인자 선언을 통해서 컴파일 타임에 Type safety(형 안정성)을 확보하기 위함
고로 무인자 자료형(Raw type)을 쓰면 형 안정성이 사라지고, 제네릭의 장점 중 하나인 표현력(expressiveness) 측면에서 손해를 보게 된다.
List
, List<Object>
, List<?>
구분
List
: 모든 타입 추가 가능 안전하지 않음List<Object>
: 모든 타입 추가 가능- 하지만
List<String>
와 호환되지 않음 - 캐스팅 불가능
- 하지만
List<?>
: 비한정적 와일드카드 자료형(unbounded wildcard type)- null 이외에 어떤 원소도 넣을 수 없음(어떤 자료형이 있는 건데 어떤 자료형이 담긴지는 알 수가 없음)
List<T extends String>
: 한정적 와일드카드 자료형(bounded wildcard type)- String 포함 String 상속 받는 타입 호환
List<String>
와 호환됨 - 캐스팅 가능
instanceof
사용법
Rule 24 - 무점검 경고(unchecked warning)를 제거하라.
example : 무점검 경고
모든 무점검 경고는, 절대로 무시하지 말아야한다. 가능한 없애야한다.
- Type safety
- No
ClassCastException
단, 제거할 없는 경고 메세지는 형 안정성이 확실할 때문 @SupressWarnings("unchecked")
어노테이션을 사용해 억제하기 바란다.
가능하면 작은 범위에 적용하라.
그리고 해당 경고를 억제한 이유를 주석으로 표현하라.
Rule 25 - 배열 대신 리스트를 써라
배열과 리스트(Collection)의 차이
- 배열 : 공변 자료형(covariant)
Sub extends Super
이면Super[] supers = new Sub[]
가능
- 리스트 : 불변 자료형(invariant)
Sub extends Super
이면List<Super>
<->List<Sub>
간 형변환 불가능- 하지만
List<Super>
->List<? extends Sub>
으로 형변환 가능
이것 이외에도 반공변 자료형(contravariant)도 있음.
- contravariant :
Child extends Parent
이면Parent[] objs = new Child[]
가능.<T super Child>
- convariant :
<T extends Parent>
참고 : https://msdn.microsoft.com/ko-kr/library/dd799517(v=vs.110).aspx
하지만 취약한 것은 배열이다. 역시 불변식이 좋음
리스트의 장점
1. 컴파일 시 타입안정성 확보
본 서적에서는 reification
을 실체화 로 표현했으나 이것은 realization 이랑 헷갈리고 개인적으로 판단할 시 개념과도 맞지 않아서 구체화 로 표현함
2. 배열은 구체화(reification) 자료형
배열의 자료형은 실행시간에 결정된다. 하지만 List
는 안됨
example
String strs = new String[] {"str1", "str2"}
구체화 불가능 자료형(non-refiable types)
실행시점(runtime)에 해당 자료형의 모든 정보를 이용할 수 없는 자료형(a type this is not completely available at runtime)이라는 뜻이라는데 명확하게 이해가 안되었으나, 이제는 이해가 되는 거 같다.
선지식으로 Java가 Generic을 구현할 시 내부적으로 타입제거(Type erasure) 를 이용하는 것이다. List<String>
의 경우 컴파일 시에는 List<String>
이지만 런타임 시에는 List
가 된다.
즉. 런타임 시에는 Parameterized type 정보인 <String>
가 없어지는 타입제거(Type erasure)가 된 후이기 때문에 해당 제네릭 정보를 알 수 없고 이것이 바로 타입의 모든 정보를 런타임 시에 알 수 없는 것이다.
E
, List<E>
, List<String>
와 같은 자료형은 구체화 불가능(non-refiable) 자료형으로 알려져 있다. - 반대로 primitive, non-generic 타입, raw 타입, 비한정 와일드카드형 타입(<?>
)은 구체화 가능(refiable) 자료형이다.
참고 : https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html
결론
구분 | Type | 구 체화 | 형안정성 |
---|---|---|---|
배열 | 공변자료형 | 가능 | 실행타임에 보장 |
제네릭 | 불변자료형 | 불가능 | 컴파일타임에 보장 |
배열과 제네릭을 뒤섞어 쓰다가 컴파일 오류나 경고메시지를 만나게되면, 배열을 리스트로 바꿔야겠다는 생각이 본능적으로 들어야 한다.
Rule 26 - 가능하면 제네릭 자료형으로 만들 것
example
배열을 사용하는 제네릭 자료형을 구현할 때 실체화 불가능한 자료형(E)으로 배열을 생성할 수 없는 경우 해결책
1. 제네릭 배열을 만들 수 없다는 조건을 우회하는 것이다.
배열 객체 생성 시 Object
배열을 만들어서 제네릭 배열 자료형으로 형변환(cast)하는 방법
example
2. 배열의 자료형을 E[]에서 Object[]로 바꾸는 것이다.
example
제네릭 배열 해결책 정리
- 무점검 형변환(unchecked cast) 경고 억제의 위험성은 스칼라(scala) 자료형 보다 배열 자료형이 더 크기 때문에, 두번째 해법이 더 낫다고 볼 수 있다.
- 하지만 현실적으로 배열을 사용하는 코드가 여러군데라면 배열 생성 시 첫번째 방법으로는 E[]으로 한번만 형변환을 하면되지만, 두번째 방법으로는 코드 여기저기에서 E로 형변환을 해야하므로 첫번째 방법이 더 낫다
결론
가능하면 하위호환성을 유지하면서 제네릭 자료형으로 리팩터링하라.
Rule 27 - 가능하면 제네릭 메서드로 만들 것
패턴
- 일반적인 사용
- 제네릭 정적 팩터리 메서드 (1.6 이하)
- 제네릭 싱글턴 팩터리 패턴
Collections.emptyList()
- 재귀적 자료형 한정(recursive type bound)
<T extends Compareable<T>> T max(List<T> list)
결론
- 제네릭 메서드는 클라이언트의 cast 비용을 줄이고 타입안정성도 높다.
- 시간 날 때 기존 메서드를 제네릭 메서드로 확장해 놓으면 기존 클라이언트 코드를 깨지 않고도 새 사용자에게 더 좋은 API를 제공할 수 있다.