ITEM 31 : 한정적 와일드카드를 사용해 API 유연성을 높여라
Last updated
Last updated
관련 용어
매개변수화 타입
parameterized type
List<String>
실제 타입 매개변수
actual type parameter
String
제네릭 타입
generic type
List<E>
정규 타입 매개변수
formal type parameter
E
비한정적 와일드카드 타입
unbounded wildcard type
List<?>
로 타입
raw type
List
한정적 타입 매개변수
bounded type parameter
<E extends Number>
재귀 타입 한정
recursive type bound
<T extends Comparable<T>>
한정적 와일드카드 타입
bounded wildcard type
List<? extends Number>
제네릭 메서드
generic method
static <E> List<E> asList(E[] a)
타입 토큰
type token
String.class
시작전, 위 개념에 대한 이해가 있어야 이해하기 쉬우며, 일부 겹치는 내용도 있다.
제네릭 매개변수화 타입(parameterized type)은 불공변이기 때문에 서로 다른 타입이다. 아래 예시를 살펴보자.
Number
와 Integer
는 서로 상속관계가 있지만 Iterable<Integer>
는 Iterable<Number>
의 하위 타입이 아니며, 아무런 관계가 없기 때문에 컴파일 오류가 발생한다. 이러한 경우 상한 경계 와일드카드(upper bounded wildcard)를 이용해서 해당 문제를 해결할 수 있다.
다음은 스택 안에 있는 원소를 모두 Object
용 컬렉션으로 옮기는 예제이다.
Collection<Object>
는 Collection<Number>
와 아무 상하관계가 없으므로, 컴파일 오류가 발생한다. 이러한 경우 하한 경계 와일드카드(lower bounded wildcard)를 사용해 해결할 수 있다.
즉, 와일드카드는 다음과 같은 관계를 갖고 있다고 볼 수 있다.
어떤 와일드 카드를 사용해야하는지 기억하는데 있어서 PECS
공식을 외워두면 도움이 될 것 이다.
PECS : producer - extends, consumer-super
즉, 매개변수화 타입 T
가 생산자인 경우 상한 경계 와일드 카드(<? extends E>
)를 사용하고, 소비자이면 하한 경계 와일드카드(<? super E>
)를 사용하라는 것이다.
추가로 예를 한개 더 살펴 볼 것이다.
ITEM30에서 살펴본 예를 와일드카드 타입을 사용해 다듬어 볼 것이다.
입력 배개변수는 E
인스턴스를 생성하므로 List<? extends E>
로 변경해주었고, Comparable
은 언제나 소비자 역할이므로, Comparable<? super E>
로 변경해주었다.
Comparable
와Comparator
는 모두 소비자이다.
ScheduledFuture
는 ScheduledFuture
인스턴스뿜 아니라 Delayed
인스턴스와도 비교할 수 있기때문에 수정전 max()
메서드에서 오류가 나는 것을 확인할 수 있다. 다시말해 Comparable
을 직접 구현하지 않고, 직접 구현한 다른 타입(Delayed
)를 확장한 타입을 지원하기 위해서 와일드카드가 필요한 것이다.
추가적으로 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하는 것이 좋다.
컴파일러는 어떠한 경우에, 와일드카드의 타입을 추론한다. 예를들어, List<?>
리스트가 정의되어있을때, 컴파일러는 코드에서 특정 타입을 추론한다. 이러한 시나리오를 와일드카드 캡처라고 한다.
"capture of" 오류 문구를 제외하고는 와일드카드 캡처를 신경쓰지 않아도 된다.
swap()
메서드에서 List.set(int, E)
를 호출할때 컴파일러는 List
에 삽입되는 객체의 유형을 확인할 수 없기 때문에 오류를 발생시킨다. 이러한 유형의 오류가 발생하면 일반적으로 컴파일러는 변수에 잘못된 타입의 값을 할당하고 있다고 믿는다는 것을 의미한다. 이 오류는 형변환이나 리스트의 로 타입을 사용하지 않고 private static helper 메서드로 해결할 수 있다.
helper 메서드 덕분에 컴파일러는 E
가 캡쳐 변수인 CAP#1
임을 추론할 수 있다. 즉, list에서 꺼낸 값의 타입은 항상 E
이고, E
타입의 값이라면 리스트에 추가해도 안전하다는 것을 컴파일러는 알 수 있다. 일반적으로 helper 메서드는 originalMethodNameHelper
로 지정된다.
와일드카드 타입으로 변경하기전의 max()
메서드는 List<ScheduledFuture<?>> scheduledFutures = ...;
리스트를 처리할 수 없었으며, 와일드카드 적용후에 오류없이 실행되는 것을 확인할 수 있다.