ITEM 31 : 한정적 와일드카드를 사용해 API 유연성을 높여라

  • 관련 용어

    한글
    영문

    매개변수화 타입

    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

시작전, 위 개념에 대한 이해가 있어야 이해하기 쉬우며, 일부 겹치는 내용도 있다.


Understanding Generics and Variance in Kotlin | by Tomek Polański | ProAndroidDev

제네릭 매개변수화 타입(parameterized type)은 불공변이기 때문에 서로 다른 타입이다. 아래 예시를 살펴보자.

NumberInteger는 서로 상속관계가 있지만 Iterable<Integer>Iterable<Number>의 하위 타입이 아니며, 아무런 관계가 없기 때문에 컴파일 오류가 발생한다. 이러한 경우 상한 경계 와일드카드(upper bounded wildcard)를 이용해서 해당 문제를 해결할 수 있다.

다음은 스택 안에 있는 원소를 모두 Object용 컬렉션으로 옮기는 예제이다.

Collection<Object>Collection<Number>와 아무 상하관계가 없으므로, 컴파일 오류가 발생한다. 이러한 경우 하한 경계 와일드카드(lower bounded wildcard)를 사용해 해결할 수 있다.

즉, 와일드카드는 다음과 같은 관계를 갖고 있다고 볼 수 있다.

PECS

어떤 와일드 카드를 사용해야하는지 기억하는데 있어서 PECS 공식을 외워두면 도움이 될 것 이다.

PECS : producer - extends, consumer-super

즉, 매개변수화 타입 T가 생산자인 경우 상한 경계 와일드 카드(<? extends E>)를 사용하고, 소비자이면 하한 경계 와일드카드(<? super E>)를 사용하라는 것이다.

추가로 예를 한개 더 살펴 볼 것이다.

ITEM30에서 살펴본 예를 와일드카드 타입을 사용해 다듬어 볼 것이다.

입력 배개변수는 E 인스턴스를 생성하므로 List<? extends E>로 변경해주었고, Comparable은 언제나 소비자 역할이므로, Comparable<? super E>로 변경해주었다.

ComparableComparator는 모두 소비자이다.

와일드카드 타입으로 변경하기전의 max() 메서드는 List<ScheduledFuture<?>> scheduledFutures = ...; 리스트를 처리할 수 없었으며, 와일드카드 적용후에 오류없이 실행되는 것을 확인할 수 있다. 아이템31 - 한정적 와일드카드를 사용해 API 유연성을 높이라

ScheduledFutureScheduledFuture 인스턴스뿜 아니라 Delayed 인스턴스와도 비교할 수 있기때문에 수정전 max() 메서드에서 오류가 나는 것을 확인할 수 있다. 다시말해 Comparable을 직접 구현하지 않고, 직접 구현한 다른 타입(Delayed)를 확장한 타입을 지원하기 위해서 와일드카드가 필요한 것이다.

추가적으로 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하는 것이 좋다.

private static helper 메서드

컴파일러는 어떠한 경우에, 와일드카드의 타입을 추론한다. 예를들어, List<?> 리스트가 정의되어있을때, 컴파일러는 코드에서 특정 타입을 추론한다. 이러한 시나리오를 와일드카드 캡처라고 한다.

"capture of" 오류 문구를 제외하고는 와일드카드 캡처를 신경쓰지 않아도 된다.

swap() 메서드에서 List.set(int, E)를 호출할때 컴파일러는 List에 삽입되는 객체의 유형을 확인할 수 없기 때문에 오류를 발생시킨다. 이러한 유형의 오류가 발생하면 일반적으로 컴파일러는 변수에 잘못된 타입의 값을 할당하고 있다고 믿는다는 것을 의미한다. 이 오류는 형변환이나 리스트의 로 타입을 사용하지 않고 private static helper 메서드로 해결할 수 있다.

helper 메서드 덕분에 컴파일러는 E가 캡쳐 변수인 CAP#1 임을 추론할 수 있다. 즉, list에서 꺼낸 값의 타입은 항상 E이고, E 타입의 값이라면 리스트에 추가해도 안전하다는 것을 컴파일러는 알 수 있다. 일반적으로 helper 메서드는 originalMethodNameHelper로 지정된다.

Last updated

Was this helpful?