제네릭
[아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라]
[핵심 정리]
[매개변수화 타입은 불공변이다.]
1. 불공변?
- List<
Type1
>와 List<Type2
>는 서로 상위 타입도 하위 타입도 아니다.
- 이는 List<
Object
>와 List<String
>을 생각하면 편한데 후자의 경우 전자의 기능을 모두 다할수 없다. String 타입만 담을 수 있기 때문이다.
[때론 불공변보다 더 유연한 무엇인가 필요하다]
1. stack의 메서드를 살펴보자
public class Stack<E>{
public void pushAll(Iterable<E> src){
for (E e : src)
push(e);
}
}
- stack을 Number타입을 이용해서 선언한다고 pushAll의 매개변수로 Iterable<
Integer
>를 넣는다고 가정해보자.
- Integer은 Number의 하위 타입으로 잘 동작한다고 생각이 되지만 실제는 오류가 난다.
- 이는 매개변수화 타입이 불공변이기 때문이다.
- Stack 클래스에서 매개변수화 타입을 Number로 줬다면 사용하는 모든 메서드 반환하는 값들은 모두 같이 맞춰주어야 한다.
2. 불공변의 한계를 해결하는 방법 - 한정적 와일드카드 타입
2-1. 한정적 와일드카드 타입?
제네릭의 제약 방법은 3가지가 존재한다.
?
: 와일드 카드로의 줄임 표현으로 모든 타입이 가능하다는 의미이다.
extends T
: 상한 경계를 두는 표현으로 T와 T의 자손만 사용 가능합니다.
super T
: 하한 경계를 두는 표현으로 T와 T의 부모만 사용 가능합니다.
2-2. 한정적 와일드카드 타입을 사용한 불공변의 한계를 해결하는 방법 (1)
public void pushAll(Iterable<? extend E> src){
for (E e : src)
push(e);
}
Iterable<? extend E> src
한정적 와일드 카드 타입을 이용해서 E의 하위 타입의 Iterable이라고 명시해주고 있다.
- 위와 같이 명시해주면 E의 Iterable이 아니고 E의 하위 타입의 Iterable이라는 뜻이 된다.
- E는 어떤 타입이 들어와도 상관 없으며 해당 타입과 해당 타입의 하위 클래스를 모두 허용한다는 의미다.
[와일드카드를 사용해야할 때와 사용하지 않아야 할때 - PECS]
- PECS
- 메서드 선언에 타입 매개변수가 한 번만 나오는 경우
1. 와일드카드를 사용하는 이유
- 유연성을 극대화하고 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용해라.
2. 사용하지 않아야 할 때
- 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드카드 타입을 사용해도 좋을 것이 없다.
3. PECS: Producer-extends / Consumer-super
- 매개변수화 타입 T가 생산자인 경우
<? extendss T>
를 사용하라
-매개변수화 타입 T가 소비자인 경우
<? super T>
를 사용하라
4. 생산자 매개변수에 와일드카드 타입 적용 코드
public void pushAll(Iterable<? extends E> src){
for (E e: c)
push(e);
}
src
가 인스턴스 E 생성을 위해서 사용되고 있기 때문에 이 경우라면 producer-extends를 사용해 와일드카드를 표현해야 한다.
5. 소비자 매개변수에 와일드카드 타입 적용 코드
public void popAll(Collections<? super E> dst){
while(!isEmpty())
dst.add(pop())
}
- 매개변수화 타입 dst가 stack에 있는 값을 소비하고 있기 때문에 소비자 매개변수에 와일드카드 타입을 적용해야 한다.
[와일드카드를 사용해야할 때와 사용하지 않아야 할때 - 메서드 선언에 타입 매개변수가 한 번만 나오는 경우]
1. 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라.
public static void swap(List<?> list, int i, int j){
list.set(i, list.set(j, list.get(i)));
}
- 해당 문제는 컴파일 시 꺼낸 원소를 다시 넣을 때 문제가 생긴다.
- 이 문제를 해결하기 위해서 private 도움 메서드로 따로 작성해 활용하는 방법이 있다.
1-1. 한 번만 나온 타입 매개변수를 오류 없이 사용하기 위한 방법
public static void swap(List<?> list, int i, int j){
swapHelper(list, i, j);
}
- 해당 메서드는 타입 매개변수가 한 번만 사용된 메서드로 와일드카드 타입의 실제 타입을 설정해주기 위해서 swapHelper()이 이를 대신하고 있다.
1-2. helper method
private static <E> void swapHelper(List<E> list, int i, int j){
list.set(i, list.set(j, list.get(i)));
}
- 와일드카드 타입의 실제 타입을 알려주는 메서드이다.
- 실제 타입을 알아내기 위해서 이 도우미 메서드는 제네릭 메서드여야 한다.