티스토리 뷰
매개변수화 타입은 불공변 → List은 List의 하위타입도 상위타입도 아니다.
때로는 불공변 방식보다 유연한 무언가가 필요함
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
//일련의 원소를 스택에 넣는 메서드 pushAll
//와일드카드 타입을 사용하지 않음 -> 결함
public void pushAll(Iterable<E> src) {
for(E e : src)
push(e);
}
위 메서드는 결함이 있다.
src의 원소타입이 스택의 원소 타입과 일치하면 잘 작동하지만,
만약 Stack 원소에 Integer 타입의 src를 넣으면 어떻게 될까?
Integer는 Number의 하위타입이기 때문에 잘 동작할 것 같지만 오류가 뜬다.
매개변수화 타입이 불공변이기 때문이다.
→ 한정적 와일드카드 타입 사용하기
한정적 와일드카드 타입
위 pushAll 메서드의 입력 매개변수 타입은 E의 Iterable이 아니라 E의 하위타입의 Iterable 이어야한다.
즉 , Iterable<? extends E> 이렇게 사용해야한다.
//와일드카드 타입 적용
public void pushAll(Iterable<? extends E> src) {
for(E e : src)
push(e);
}
popAll메서드는? Stack안의 모든 원소를 주어진 컬렉션으로 옮겨 담아야하기 때문에 E의 상위 타입의 Collection이어야한다. 즉, Collection<? super E> 이렇게 사용해야한다.
//와일드카드 타입 적용
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
유연성을 극대화하기 위해서는 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용해라.
(PECS 공식 : T가 생산자면 <? extends T>, T가 소비자면 <? super T> )
제대로 사용하면 클라이언트는 와일드카드 타입이 쓰였다는 것도 모른다.
클라이언트가 와일드카드 타입을 신경써야한다면 API에 문제가 있을 가능성이 크다.
자바7
자바7이하에서는 위의 변경이 소용이 없다.
명시적 타입인수를 사용해서 타입을 알려줘야한다.
Set<Integer> integers = Set.of(1,3,5);
Set<Double> doubles = Set.of(2.0, 4.0, 6.0);
//명시적 타입인수
Set<Number> numbers = Union.<Number>(integers,doubles);
Comparable 과 Comparator
Comparable과 Comparator은 언제나 소비자이기 때문에
보다는 <? super E>를 사용하는것이 낫다.
타입 매개변수와 와일드 카드
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
메서드 선언에 타입 매개변수가 한번만 나오면 와일드 카드로 대체하라 (두번째)
이 때 비한정적 타입 매개변수면 비한정적 와일드카드로 바꾸고, 한정적 타입 매개변수면 한정적 와일드카드로.
단 두번째 swap은 문제가 있다. 비한정적 와일드카드 List<?>은 null만 넣을 수 있기 때문이다.
→ 실제타입을 알려줄 도우미 메서드 사용으로 해결 (도우미 메서드는 제너릭 메서드여야함)
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
//도우미 메서드가 리스트의 타입이 항상 E인것을 알기 때문에 안전함을 알고 있다.
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
결론
조금 복잡하더라도 와일드카드 타입을 적용하면 api가 훨씬 유연해진다.
PECS 공식을 기억하자. (생산자는 extends, 소비자는 super 사용)
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] 33.타입 안전 이종 컨테이너를 고려하라 (0) | 2022.03.23 |
---|---|
[Effective Java] 32.제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.03.23 |
[Effective Java] 30.이왕이면 제네릭 메서드로 만들라 (0) | 2022.03.23 |
[Effective Java] 29.이왕이면 제네릭 타입으로 만들라 (0) | 2022.03.23 |
[Effective Java] 28.배열보다는 리스트를 사용하라 (0) | 2022.03.23 |