조금 복잡하더라도 와일드카드 타입을 적용하면 API 가 훨씬 유연해진다. 그러니 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용해줘야 한다. PECS 공식을 기억하자. producer-extends, consumer-super. Comparable과 Comparator는 모두 소비자라는 사실도 잊지 말자.
이전의 Stack<E> 에서의 문제점
pushAll 메서드의 문제점
public void pushAll(Iterable<E> src) { for (E e : src) push(e); } Stack<Number> numberStack = new Stack<>(); Iterable<Integer> integers = ...; numberStack.pushAll(integers); // 에러 /* StackTest.java : error : incompatiable types: Iterable<Integer> cannot be converted to Iterable<Number> */
- 위와 같은 상황에 대처할 수 있는 방법이
한정적 와일드카드 타입
이라는 특별한 매개변수화 타입임
Iterable<E> src
->Iterable<? extends E> src
- Stack이 사용할 E 인스턴스를 생산하므로 생산자
popAll 메서드의 문제점
public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); }
- 수정 :
Collection<E> dst
→Collection<? super E> dst
- Stack으로부터 E 인스턴스를 소비하므로 소비자
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라. 만약, 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드카드 타입을 써도 좋을 게 없다. 이때는 타입을 정확히 지정해야 하는 상황으로, 이때는 와일드 카드 타입을 쓰지 말아야 한다.
펙스(PECS) : producer-extends, consumer-super
PECS 적용 예시
예시 1
// 수정 전 public static <E> Set<E> union(Set<E> s1, Set<E> s2) // 수정 후 public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
- 반환 타입은 여전히 Set<E> 임.
반환 타입에는 한정적 와일드카드 타입을 사용하면 안 된다.
유연성을 놓여주기는 커녕 클라이언트 코드에서도 와일드카드 타입을 써야 하기 때문
매개변수( parameter) 와 인수(argument)의 차이. 매개변수는 메서드 선언에 정의한 변수이고, 인수는 메서드 호출 시 넘기는 ‘실젯값'이다. void add(int value) { … } add(10) 해당 코드에서 value는 매개변수이고 10은 인수임 class Set<T> { … } Set<Integer> = … ; T는 타입 매개변수가 되고, Integer는 타입 인수가 됨
예시 2
// 수정 전 public static <E extends Comparable<E>> E max(List<E> list) // 수정 후 public static <E extends Comparable<? super E>> E max(List<? extends E> list)
- PECS 공식 두 번 적용
- 입력 매개변수에서는 E 인스턴스를 생산하므로 <? extends E> 적용 (producer-extends)
- Comparable<E>는 E 인스턴스를 소비하고 선후 관계를 뜻하는 정수를 생산함. <? super E>로 대체 (consumer-super)
- Comparable(혹은 Comparaotr)을 직접 구현하지 않고, 직접 구현한 다른 타입을 확장한 타입을 지원하기 위해 와일드카드가 필요하다.
타입 매개변수와 와일드카드 둘 중 어느 것을 사용?
기본 규칙 : 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라
public static <E> void swap(List<E> list, int i, int j); public static void swap(List<?> list, int i, int j);
- public API라면 간단한 두 번째가 낫다.( 클라이언트가 복잡한 제네릭을 알 필요가 없으니까)
- 그러나 두 번째로 쓸 때, private 도우미 메서드로 generic 메서드를 만들어 주어야 함(그럼에도, 클라이언트는 그걸 모르고 쓸 수 있으니 좋다고 한다)
- 외부에서는 와일드카드 기반의 멋진 선언을 유지할 수 있다
public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); // 위 코드를 swap에서 바로 호출하면, List<?> 에는 null 값 외에는 어떤 값도 넣을 수 없기에 // 문제가 발생함 }