Sub[]도 Super[]의 하위 타입
-하지만! List<Sub>은 List<Super>의 하위 타입도, 상위 타입도 아닙니다.
List<String>은 List<object>의 하위 타입이 아니라는 뜻
List<Object>에는 어떤 객체든 넣을 수 있지만, List<String>에는 문자열만 넣을 수 있음.
List<String>은 List<Object>가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없음
한정적 와일드카드를 사용하자
28 배열보다 리스트를 사용하라고 했음
배열은 런타임에 원소의 타입을 인지
제네릭은 타입 정보를 컴파일시 인지
하지만, 때론 불공변 방식보다 유연한 무언가가 필요하다
case1. Stack의 pushAll 메서드 구현
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);
}
}
- Iterable src의 원소 타입이 스택의 원소 타입과 일치하면 잘 동작
```JAVA
Stack<Integer> integerStack = new Stack<>();
Iterable<Integer> intVal = List.of(3, 5);
integerStack.pushAll(intVal); // ok
그럼 Stack<Number>로 선언한 후 pushAll(intVal)을 호출하면 어떻게 될까?
Collection <Number>는 Collection<Integer>의 하위 타입이 아니므로 오류 발생 !
해결법 : 한정적 와일드카드 타입으로 변경
popAll의 입력 매개변수의 타입이 'E의 Collection'이 아니라 'E의 상위 타입의 Collection'이어야 함
Collection<? super E>
// ok
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
공식 : 팩스(PECS) producer-extends, consumer-super
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라 !
매개변수화 타입 T가 생산자라면 <? extends T>
ex) Stack에서 pushAll의 src 매개변수는 Stack이 사용하는 E 인스턴스를 생산하므로 extends
매개변수화 타입 T가 소비자라면 <? super T>
ex) Stack에서 popAll의 dst 매개변수는 Stack으로부터 E 인스턴스를 소비하므로 super
다만, 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드카드 타입을 쓰지 말아야 함
타입을 정확히 지정해야 하는 상황
case3. #28 의 Chooser 생성자
class Chooser<T> {
private final List<T> choiceList;
public Chooser(Collection<T> choices) {
this.choiceList = new ArrayList<>(choices);
}
...
}
- 생성자에 넘겨지는 choices 컬렉션은 T 타입의 값을 생산하기만 함
- `Chooser<Number>`의 생성자에 `List<Integer>`를 매개변수로 넘긴다면?
- **컴파일 에러**
> 해결법 : PECS 공식에 따라 매개변수 Collection 원소 타입이 생산자이므로 Producer-extends 규칙에 따라 변경
```JAVA
public Chooser(Collection<? extends T> choices){...} // ok
Chooser<Number>의 생성자에 List<Integer>를 매개변수로 넘길 수 있게 됨
case4. #30-2 unoin 메서드
public class GenericMethodTest {
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
// 원래 버전
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 공식을 두번 적용
- 입력 매개변수 : `List<E> list -> List<? extends E> `
- E 인스턴스를 생산하므로 Producer-Extends 공식에 의해 수정되어짐
- 타입 매개변수 : `<E extends Comparable> -> <E extends Comparable<? super E>>`
- 처음 E가 `Comparable<E>`를 확장한다고 정의했는데, 이때` Comparable<E>`는 E 인스턴스를 소비한다
- 그래서 Consumer-Super 공식에 의해 수정되어짐
- Comparable은 언제나 소비자이므로, 일반적으로 `Comparable<E>`보다는 `Comparable<? super E>`를 사용 (Comparator도 마찬가지)
> 이렇게 복잡하게 만들 가치가 있는걸까?
- 다음 리스트는 오직 수정된 max로만 처리할 수 있음
```java
List<ScheduledFuture<?>> scheduledFutures = ...;
수정 전 max는 ScheduledFuture가 Comparable<ScheduledFuture>를 구현하지 않았기 때문
public interface Comparable<E>
public interface Delayed extends Comparable<Delayed>
public interface ScheduledFuture<V> extends Delayed, Future<V>
SchueduledFuture의 인스턴스는 다른 ScheduledFuture 인스턴스뿐 아니라 Delayed 인스턴스와도 비교할 수 있어서 수정 전 max가 이 리스트를 거부
더 일반화하면, Comparable(혹은 Comparator)을 직접 구현하지 않고, 직접 구현한 다른 타입을 확장한 타입을 지원하기 위해 와일드카드가 필요
메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라
메서드를 정의할 때 둘 중 어느 것을 사용해도 괜찮을 때가 많음
주어진 리스트에서 명시한 두 인덱스의 아이템들을 교환(swap)하는 정적 메서드를 두 방식으로 정의하면 다음과 같다
// 1. 비한정적 타입 매개변수 사용
public static <E> void swap(List<E> list, int i, int j);
// 2. 비한정적 와일드카드 사용
public static void swap(List<?> list, int i, int j);
-public API라면 간단한 두번째가 나음
- 어떤 리스트든 이 메서드에 넘기면 명시한 인덱스의 원소들을 교환
- 신경 써야 할 타입 매개변수도 없음
## 규칙 : 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라
- 비한정적 타입 매개변수라면 비한정적 와일드카드로 바꾸고, 한정적 타입 매개변수라면 한정적 와일드카드로 바꿔라
> case1. 방금 꺼낸 원소를 리스트에 다시 넣는 코드
```java
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i))); // error
}
방금 꺼낸 원소를 리스트에 다시 넣을 수 없다는 뜻
List<?>에는 null 외에는 어떤 값도 넣을 수 없기 때문
해결법 : 와일드카드 타입의 실제 타입을 알려주는 메서드 구현하라
private 도우미 메서드로 따로 작성하여 활용하는 방법임
실제 타입을 알아내려면 이 도우미 메서드는 제네릭 메서드여야 함
public static void swap(List<?> list, int i, int j){
swapHelper(list, i, j);
}
// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드
private static void swapHelper(List list, int i, int j){
list.set(i, list.set(j, list.get(i)));
}
- swapHelper 메서드는 리스트가 `List<E>`임을 알고 있음
- 즉, 이 리스트에서 꺼낸 값은 항상 E이고, E타입의 값이라면 이 리스트에 넣어도 안전함을 알고 있음
# 정리
- 조금 복잡하더라도 와일드카드 타입을 적용하면 API가 훨씬 유연해진다!
- 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용하자!
- PECS 공식을 기억하자!
- Comparable과 Comparator는 모두 소비자다!
매개변수화 타입은 불공변(invariant) #28
Sub
은Super
의 하위 타입Sub[]
도Super[]
의 하위 타입 -하지만!List<Sub>
은List<Super>
의 하위 타입도, 상위 타입도 아닙니다.<String>
은 List<object>
의 하위 타입이 아니라는 뜻<Object>
에는 어떤 객체든 넣을 수 있지만, List<String>
에는 문자열만 넣을 수 있음.List
<String>
은 List<Object>
가 하는 일을 제대로 수행하지 못하니 하위 타입이 될 수 없음한정적 와일드카드를 사용하자
28 배열보다 리스트를 사용하라고 했음
} }
Stack<Number>
로 선언한 후 pushAll(intVal)을 호출하면 어떻게 될까?Iterable<Integer>
cannot be converted toIterable<Number>
공식 : 팩스(PECS) producer-extends, consumer-super
<? extends T>
<? super T>
... }
Chooser<Number>
의 생성자에List<Integer>
를 매개변수로 넘길 수 있게 됨}
Set<Number>
이다.// 와일드카드 타입 사용 public static <E extends Comparable<? super E>> E max(List<? extends E> list)
Comparable<ScheduledFuture>
를 구현하지 않았기 때문Comparable<Delayed>
를 확장메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라
// 2. 비한정적 와일드카드 사용 public static void swap(List<?> list, int i, int j);
List<?>
에는 null 외에는 어떤 값도 넣을 수 없기 때문// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 private static void swapHelper(List list, int i, int j){
list.set(i, list.set(j, list.get(i)));
}