매개변수화 타입의 불공변성
// Integer 타입은 Number 타입을 상속했기때문에, num = intNum가 가능하다.
Number num = 1;
Integer intNum = 1;
num = intNum;
// Integer 타입은 Number 타입을 상속했지만, 매개변수 타입으로 전달되었을때는 그 관계가 유지되지 않는다.
// 따라서 numStack = intStack 코드는 컴파일 에러가 발생한다.
Stack<Number> numStack = new Stack<>();
Stack<Integer> intStack = new Stack<>();
numStack = intStack;
매개변수화 타입은 불공변이다. 불공변이라는 말은 타입의 관계가 같이 변하지 않는다는 것을 말한다. 위의 코드를 보면 Integer 타입은 Number 타입을 상속하였기때문에 두 타입은 부모/자식의 관계가 성립한다. 하지만 두타입이 매개변수 타입으로 사용된 Stack<Number>와 Stack<Integer>는 부모/자식 관계가 성립하지 않는다.
public class Stack<T> {
...
// 여러 원소들을 스택에 넣는 pushAll 메소드 추가
public void pushAll(Iterable<T> src) {
for(T e : src) {
this.push(e);
}
}
...
}
List<Number> nums = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
Stack<Number> numStack = new Stack<>();
numStack.pushAll(nums);
// List<Integer> 타입은 Iterable<Number>와 관계가 없기때문에 컴파일 에러 발생
numStack.pushAll(ints);
매개변수 타입의 불공변성은 유연한 코드 작성을 방해한다. 여러 원소를 한번에 넣기위해 pushAll 함수를 만들었지만 Number의 하위 타입인 Integer를 원소로 가지고있는 List<Integer> 객체를 넣으려고 하니 컴파일 에러가 발생하였다. push 메소드에는 Integer 타입을 넣을 수 있는걸 생각하면 웃긴 일이다.
생산자 매개변수에 와일드 카드 적용
public class Stack<T> {
...
// 여러 원소들을 스택에 넣는 pushAll 메소드 추가
public void pushAll(Iterable<? extends T> src) {
for(T e : src) {
this.push(e);
}
}
...
}
List<Number> nums = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
Stack<Number> numStack = new Stack<>();
numStack.pushAll(nums);
numStack.pushAll(ints);
와일드 카드를 사용하면 유연한 코드를 작성할 수 있다. 기존 pushAll 메소드에서 매개변수의 타입을 Iterable<? extends T>로 바꾸었다. 이는 T 타입을 상속하는 타입 매개변수를 가지는 Iterable까지 매개변수로 받겠다는 의미이다. 이제 Stack<Number>의 pushAll메소드에 List<Integer>를 넣을 수 있다.
소비자 매개변수에 와일드카드 적용
public class Stack<T> {
...
public void popAll(Collection<? super T> dst) {
while(!this.isEmpty()) {
dst.add(this.pop());
}
}
...
}
// Integer의 부모관계인 Number 타입의 List로 popAll을 수행하였다.
Stack<Integer> intStack = new Stack<>();
List<Number> nums = new ArrayList<>();
intStack.popAll(nums);
Stack의 모든 요소를 꺼내는 popAll 메소드 역시 와일드 카드를 사용하여 유연한 코드를 작성하였다. Collection<? super T>는 T 타입의 부모를 타입매개변수로 가지는 Collection도 매개변수로 받겠다는 의미이다. 이 덕분에 Stack<Integer>의 popAll 매개변수로 List<Number> 타입을 전달할 수 있다.
의외의 다양한 와일드카드 활용
// 생성자
public Chooser(Collection<? extends T> choices) {
choiceList = new ArrayList<>(choices);
}
// union 메소드
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
// 재귀적 타입 한정에도 와일드카드를 사용하였다.
public static <E extends Comparable<? super E>> E max(List<? extends E> list) {
if (list.isEmpty())
throw new IllegalArgumentException("Empty list");
E result = null;
for (E e : list)
if (result == null || e.compareTo(result) > 0)
result = e;
return result;
}
'STUDY > 이펙티브자바' 카테고리의 다른 글
5-7) 타입 안전 이종 컨테이너를 고려하라 (0) | 2022.10.21 |
---|---|
5-6) 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.10.21 |
5-4) 이왕이면 제네릭 타입으로 만들라 (0) | 2022.10.19 |
5-3) 배열보다는 리스트를 사용하라 (0) | 2022.10.18 |
5-2) 비검사 경고를 제거하라 (0) | 2022.10.18 |