시작 하기 전에 : 매개변수화 타입 : 불공변 -> "서로 상-하위 타입의 관계가 아니다" 라는 것을 기억해야 한다.
List가 상-하위 타입이 아닌 이유는 다 알 것이라 생각.. 리스코프 치환 원칙에도 어긋나고, 말도 안된다.
그럼, 불공변 방식보다 유연한 타입은 없을까?
-> 한정적 와일드카드 타입!
"한정적 와일드카드 타입을 사용하지 않은 메서드"
public void pushAll(Iterable<E> src) {
for (E e : src) {
push(e);
}
}
"컴파일은 되지만, 결함 존재"
// Iterable src원소 타입이 스택의 원소 타입과 일치할 때만 잘 작동한다.
// 그렇지 않을 경우 매개변수화 타입은 불공변이기 때문에 imcompatible type error 발생
"한정적 와일드카드 타입 ?를 사용한 경우"
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e);
}
}
"Iterable<? extends E> : E의 자기 자신도 포함하는 하위 타입의 Iterable"
// 타입 안전하다.
"한정적 와일드카드 타입을 사용하지 않은 경우"
public void popAll(Collection<E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
"주어진 컬렉션 원소 타입이 스택 원소 타입과 일치할 때만 잘 작동된다."
"한정적 와일드카드 타입을 사용한 경우"
public void popAll(Collection<? super E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
"Collection<? super E> : E는 자기 자신을 포함하는 상위 타입의 Collection이어야 한다."
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라.
입력 매개변수가 생산자, 소비자 역할을 동시에 한다면? 타입을 정확히 지정해야 한다. -> 즉, 와일드 카드 타입은 쓰지 말 것
- 생산자 : 입력 매개변수로부터 이 컬렉션으로 원소를 옮겨 담는다는 뜻
- 소비자 : 컬렉션 인스턴스의 원소를 입력 매개변수로 옮겨 담는다는 뜻
어떤 와일드 카드 타입을 사용해야 할까?
와일드 카드 타입을 사용하는 기본 원칙 (겟 풋 원칙 이라고도 부른다.)
PECS(Producer-Extends, Consumer-Super)
매개변수화 타입 T가 생산자라면 <? extends T>를 사용하고, 소비자라면 <? super T>를 사용하라.
* 주의 사항 *
반환 타입에는 한정적 와일드카드 타입을 사용하면 안된다.
- 유연성을 높여주는 역할을 하지 못한다.
- 클라이언트 코드에서 와일드 카드 타입을 사용/신경써야 하기 때문에 문제가 있는 API가 될 가능성이 커진다.
컴파일러가 올바른 타입을 추론하지 못할 때는 언제든지 명시적 타입 인수를 사용해 타입을 알려주자.
Comparable<E>는 E 인스턴스를 소비한다. 또한, 선후 관계를 뜻하는 정수를 생산한다.
따라서 매개변수화 타입보다는 한정적 와일드카드 타입으로 대체하는 것이 낫다.
Comparable은 언제나 소비자이므로, Comparable<E> 보다는 Comparable<? super E>를 사용하자.
Comparator은 언제나 소비자이므로, Comparator<E> 보다는 Comparator<? super E>를 사용하자.
Comparable 또는 Comparator를 직접 구현하지 않고,
'직접 구현한 다른 타입을 확장한 타입'을 지원하기 위해서는 와일드카드 타입이 필요하다.
타입 매개변수와 와일드드 타입은 공통되는 부분이 있어 메서드를 정의할 때 둘 중 아무거나 사용해도 괜찮은 경우가 있다.
그러나 더 좋은건 분명히 있다.
"비한정적 타입 매개변수 사용"
public static <E> void swap(List<E> list, int i, int j);
"비한정적 와일드카드 사용"
public static void swap(List<?> list, int i, int j);
// public API인 경우에 더 낫다.
// 어떤 리스트던 이 메서드에 넘기면 명시한 인덱스 원소들을 교환해 줄 것이고,
// 신경써야 할 타입 매개변수도 없기 때문이다.
* 기본 규칙 *
메서드 선언에 타입 매개변수가 한 번만 나오면 와일드 카드로 써라.
-> (비)한정적 타입 매개변수라면 (비)한정적 와일드카드 타입으로 바꾸어서 구현하라는 의미이다.
- 한정적 와일드카드 : 특정 타입을 제한. extend, super ..
- 비한장적 와일드카드 : 어떤 타입이 오던 관계가 없다는 뜻. <?>
"비한정적 와일드카드 사용"
-> 오류가 있는 코드. List<?>에는 null 외에는 어떤 값도 넣을수가 없기 때문에 생긴다.
public static void swap(List<?> list, int i, int j){
list.set(i, list.set(j, list.get(i)));
}
/*----------오류 해결 : private 도우미 메서드 구현---------------*/
public static void swap(List<?> list, int i, int j){
swapHelper(list, i, j);
}
//와일드카드 타입을 실제 타입으로 변경해준다. 실제 타입을 알아내기 위해선 제네릭 메서드여야 한다.
public static <E> void swapHelper(List<E> list, int i, int j) {
//얘는 리스트가 List<E>라는 것을 알고 있다.
//즉, 리스트에서 꺼낸 값의 타입이 항상 E이고 E타입의 값을 넣으면 안전하다는 것을 알고 있다.
list.set(i, list.set(j, list.get(i)));
}
/* 핵심 정리 */
널리 쓰일 라이브러리를 작성한다면, 유연한 API를 위해 와일드카드 타입을 사용하자.
생산자는 extends, 소비자는 super을 사용한다.
'JAVA > Effective Java' 카테고리의 다른 글
item 34. 상수 대신 열거 타입을 사용하라 (0) | 2022.05.31 |
---|---|
item 33. 타입 안전 이종 컨테이너를 고려하라. (0) | 2022.05.17 |
item 30. 이왕이면 제네릭 메서드로 만들어라 (0) | 2022.05.14 |
item 29. 이왕이면 제네릭 타입으로 만들어라 (0) | 2022.05.13 |
item 28. 배열보다는 리스트를 사용해라 (0) | 2022.05.13 |
댓글