[Item 31] 한정적 와일드카드를 사용해 API 유연성을 높이라
제네릭 타입 즉, 매개변수화 타입은 불공변(invariant)이다. 아이템 28에서 살펴봤을 것이다.
하지만 불공변 방식보다 유연한 무언가가 필요하다.
아래와 같은 스택 구조가 있다고 가정해보자.
public class Stack<E> {
private Object[] objects;
private int index;
private int top = -1;
public Stack(int size) {
this.objects = new Object[size];
}
public void push(E e) {
this.objects[index] = e;
index++;
}
@SuppressWarnings("unchecked")
public E pop() {
E element = (E) this.objects[index];
--index;
return element;
}
public void pushAll(Iterable<E> src) {
for (E e : src) {
push(e);
}
}
}
이러한 구조는 깨끗히 컴파일이 되지 않지만 Iterable<E> 원소 타입이 스택의 원소 타입과 일치하면 잘 작동한다.
하지만 Stack<Number>로 선언한 후 pushAll(intVal) 을 호출하면 어떻게 될까?
매개변수화 타입은 불공변이여서 컴파일 에러가 발생한다.
public static void main(String[] args) {
Stack<Number> stack = new Stack<>(10);
Iterable<Integer> integers = new ArrayList<>();
stack.pushAll(integers);// 컴파일 에러
}
컴파일 에러를 해결 하려면 한정적 와일드카드 타입을 제공해야 한다.
생산자(producer) 매개변수에 와일드카드 적용
값을 제공하여 저장하는 행위여서 extends 키워드를 사용한다.
public void pushAll(Iterable<? extends E> src) {
for (E e : src) {
push(e);
}
}
컴파일 에러가 깔끔히 제거가 되어 타입에 안정성을 보장해준다.
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일트카드 타입을 사용하라
다음으로 popAll(Collection<E> collection) 메서드를 살펴보자.
소비자 매개변수에 와일드카드 타입 적용
값을 사용하는 입장으로서 super 키워드를 사용한다.
public void popAll(Collection<E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
이것 또한 컴파일 에러가 날 것이다.
이 문제는 super 키워드를 사용하여 풀어나가보자.
public void popAll(Collection<? super E> dst) {
while (!isEmpty()) {
dst.add(pop());
}
}
주의
반환 타입에는 한정적 와일드카드 타입을 사용하면 안 된다.
유연성을 높여주지 않고 클라이언트에서도 한정적 와일드카드 타입을 써야 하기 때문에 문제가 있다.
Comparable<E>를 사용하여 확장하는 경우 한정적 와일드카드 타입을 사용하는 편이 낫다.
public static <E extends Comparable<? super E>> E max(List<E> list)
Comparable은 원소를 사용하는 입장이므로 super키워드를 사용한다.
메서드 선언에 타입 매개변수가 한 번만 나오면 와일드 카드로 대체하라
swap 하는 메서드를 살펴보자
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
이 코드는 컴파일 에러가 날 것이다. 이유는 <?> 타입이 null 외에는 어떤 값도 넣을 수 없어서 에러가 발생한다.
코드를 개선해보자.
public static void swap(List<?> list, int i, int j) {
helpSwap(list, i, j);
}
private static <E> void helpSwap(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
private 도우미 메서드를 생성하고 <E> 타입으로 만든다.
이 List<E> 에서 꺼낸 원소는 항상 E 이고, E 타입의 값이라면 이 리스트에 넣어도 안전함을 할고 있다.
정리
- 조금 복잡하더라도 와일드카드 타입을 적용하면 API가 훨씬 유연해진다.
- 생산자는 extends 소비자는 super.
- Comparable과 Comparator는 모두 소비자다.