Open youngkimi opened 3 months ago
네, 클래스를 만들 때에도 와일드카드(?)를 사용할 수 있습니다. 와일드카드는 제네릭 타입에서 사용되며, 타입 파라미터를 알 수 없는 경우에 유용하게 활용됩니다.
와일드카드는 보통 제네릭 클래스, 제네릭 메서드, 제네릭 인터페이스의 타입 파라미터로 사용됩니다. 와일드카드는 다음과 같은 세 가지 형태로 사용될 수 있습니다:
<?>: 모든 타입을 나타냅니다. 제네릭 클래스나 메서드에 와일드카드를 사용하면 해당 위치에 어떤 타입이든 사용할 수 있습니다.
<? extends T>: 상한 경계 와일드카드입니다. T의 하위 클래스들을 나타냅니다. 따라서 T 자신이나 T의 하위 클래스의 인스턴스만 사용할 수 있습니다.
<? super T>: 하한 경계 와일드카드입니다. T의 상위 클래스들을 나타냅니다. 따라서 T 자신이나 T의 상위 클래스의 인스턴스만 사용할 수 있습니다.
와일드카드는 제네릭 타입을 더 유연하게 만들어주며, 다양한 상황에서 유용하게 사용될 수 있습니다.
Chapter : 5. 제네릭
Item : 31. 한정적 와일드카드를 사용해 API 유연성을 높이라.
Assignee : youngkimi
🍑 서론
오늘 배울 것
<? extends E>
,<? super E>
의 의미에 대해 알아보자.매개변수화 타입은 불공변
서로 다른 타입 Type1, Type2가 있을 때, List 은 List의 상위 타입도, 하위 타입도 아니다.
마찬가지로 List
그래도 때로는 유연함이 필요하다.
item 29의 Stack 클래스를 떠올려보자.
여기에 아래
pushAll()
이라는 메서드를 추가해보자이 메서드는 컴파일되지만 완벽하지 않다.로 선언 후
Iterable
src 원소 타입이 스택의 원소 타입과 일치하면 잘 작동한다. 하지만 StackNumber
의 하위 타입인Integer
를pushAll()
의 매개변수로 전달하면 어떻게 될까?Integer는 Number의 하위 타입이니 논리적으로 잘 동작해야할 것 같다. 하지만 매개변수화 타입이 불공변이므로 오류 메세지가 뜬다.
자바는 이런 상황에 대처할 수 있는 한정적 와일드카드 타입이라는 특별한 매개변수화 타입을 지원한다.
🍑 본론
한정적 와일드카드 타입
요구사항 :
pushAll()
의 입력 매개변수 타입은E의 Iterable
이 아니라E의 하위 타입의 Iterable
이다. 와일드 카드 타입Iterable<? extends E>
가 바로 그런 의미이다.그럼 위에서 작성한
pushAll()
을 한정적 와일드카드 타입을 사용해 수정해보자.이렇게 수정하면 Stack, Client 코드 모두에서 말끔히 컴파일된다. (타입이 안전하다.)
이번에는
pushAll()
과 반대되는popAll()
을 작성해보자.popAll()
은 Stack 내의 모든 원소를 주어진 컬렉션으로 옮겨 담는다.위에서 발생했던 문제처럼,
Stack<Number>
의 원소를List<Object>
으로 옮겨담을 때 문제가 발생할 것이다. 이번에는 E의 Collection이 아니라, E의 상위 타입의 Collection이면 될 것이다. 와일드 카드 타입Collection<? super E>
이 그런 의미이다.유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드 카드 타입을 사용하라.
한편, 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면, 이때는 타입을 정확하게 지정해야하는 상황으로 와일드카드를 사용하지 말아야 한다.
언제 어떤 와일드 카드를 사용할까? (PECS)
PECS : Producer-extends, Consumer-super
즉, 매개변수화 타입 T가 생산자라면 <? extends E>를 사용하고, 소비자라면 <? super E>를 사용하라.
Stack의 예시를 다시 참조하자.
pushAll()
의 src 매개변수는 Stack이 사용할 E 인스턴스를생산
하므로, src의 적절한 타입은Iterable<? extends E>
이다.반면
popAll()
의 dst 매개변수는 Stack의 E 인스턴스를소비
하므로, dst의 적절한 타입은Collection<? super E>
이다.코드 30-2의
union()
을 보자.s1, s2 모두 E의 생산자이므로 다음과 같이 선언해야 한다.
앞의 코드는 자바 8 부터 제대로 컴파일된다. 자바 7까지는 타입 추론 능력이 충분히 강력하기 못해 문맥에 맞는 반환(목표) 타입을 명시해야 했다. 이런 경우에는 명시적 타입 인수로 타입을 알려주면 되었다.
타입 매개변수와 와일드카드
타입 매개변수와 와일드카드에는 공통되는 부분이 있어서, 메서드를 정의할 때 둘 중 어느 것을 사용해도 좋을 때가 있다.
public API라면 간단한 두 번째가 낫다. 어떤 리스트든 이 메서드에 넣으면 명시한 인덱스의 원소를 교환해 줄 것이다. 신경 써야할 타입 매개변수도 없다.
하지만 두 번째 swap 선언에는 문제가 있다.
이 코드를 컴파일하면 방금 꺼낸 원소를 다시 리스트에 넣을 수 없다는 오류 메세지가 나온다. 원인은 List의 타입이
List<?>
인데,List<?>
에는 null 외에는 어떠한 값도 넣을 수 없기 때문이다. 다행히 (런타임 오류를 낼 수 있는) 형변환이나 리스트의 로 타입을 사용하지 않고 해결하는 방법이 있다. 와일드카드 타입의 실제 타입을 알려주는 private 도우미 메서드로 작성하여 활용하는 것이다.swapHelper()
은 리스트가 Listswap()
메서드를 호출하는 클라이언드는swapHelper()
와 같이 복잡한 내부 존재는 모른 채 혜택을 누릴 수 있다.🍑 결론
extends
가 썩 잘 어울리는 단어는 아니다. 하위 타입이란 자기 자신도 포함하지만, 그렇다고 자신을 확장한 것은 아니기 때문이다. (item 29)조금 복잡하더라도 와일드카드 타입을 적용하면 API가 훨씬 유연해진다. 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용해야 한다.
PECS 원칙을 기억하자.
Comparable과 Comparator는 모두 소비자이다.
Referenced by
-