제네릭 타입 즉, 매개변수화 타입은 불공변(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는 모두 소비자다.
'Book > Effective Java' 카테고리의 다른 글
[Item 34] int 상수 대신 열거 타입을 사용하라 (0) | 2022.09.02 |
---|---|
[Item 33] 타입 안전 이종 컨테이너를 고려하라 (0) | 2022.09.02 |
[Item 32] 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.08.31 |
[Item 30] 이왕이면 제네릭 메서드로 만들라 (0) | 2022.08.30 |
[Item 29] 이왕이면 제네릭 타입으로 만들라 (0) | 2022.08.18 |
[Item 28] 배열보다는 리스트를 사용하라 (0) | 2022.08.18 |
[Item 27] 비검사 경고를 제거하라 (0) | 2022.08.06 |
[Item 26] 로 타입(Raw Type)은 사용하지 말라 (0) | 2022.08.05 |