본문 바로가기
Java

Item31. 한정적 와일드카드를 사용해 API 유연성을 높이라.

by 쁘니쁘나 2022. 6. 4.

 

일단 와일드카드가 뭐지???

 

 

내가 알고있는 와일드카드는 비장의카드, 좋은카드.. 모 이런의미뿐… 허허;;

와일드카드

제네릭 코드에서 물음표(?) 로 표기되어 있는것으로, 아직 알려지지 않은 타입을 뜻한다.

 

 

그럼 제네릭은 뭐지…?

 

 

분명.. 많이 들어는봤지만 알아볼 생각을… 지금하면되지..!!!!

 

 

제네릭(Generic)

데이터의 타입을 일반화하는것을 의미한다.

클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법!

 

아래와 같이 자바에서 제네릭은 클래스와 메소드에만 다음과 같은 방법으로 선언할 수 있다. T는 타입변수 라고 하며, 임의의 참조형 타입을 의미한다.

class MyArray<T> {

    T element;

    void setElement(T element) { this.element = element; }

    T getElement() { return element; }

}

 

위와 예제에서 선언된 제네릭 클래스를 생성할 때에는 타입 변수 자리에 사용할 실제 타입을 아래와 같이 명시해야한다.

MyArray<Integer> myArr = new MyArray<>();

 

그냥 쓰기만하고 이게 제네릭이였다는 것은 몰랐다…허허;;

 

 

다시 그럼 본론으로 넘어와서..!

와일드카드는 이제 뭔지 알겠는데 한정적 와일드카드는 또 모야??

일단 비한정적 와일드 카드부터 알아보자! 그래야 한정적 와일드카드 설명하기가 편안....

 

 

비한정적 와일드카드 (Unbounded Wildcards)

와일드카드 문자인 물음표(?)만 사용할 때 비한정적 와일드카드라고 한다.

알려지지 않은 타입의 리스트 라고도 불리며, 다음과 같은 상황일 때 비한정적 와일드카드가 쓰인다.

 

1. Object class에서 제공하는 메서드일 때

2. 매개변수 타입에 의존하지 않는 제네릭 클래스의 메서드를 사용할 때

 

메서드 printList(List<Object>) 의 경우 어떤 타입이라도 상관없이 출력하려고 만들었지만, 정작 List의 원소로든 Object 타입만 허용한다.

하지만, List<String> 은 List<Object> 의 하위타입이라 할 수 없다…!! 왜???

List<Object> 에는 어떤 객체든 넣을 수 있지만, List<String> 에는 문자열만 넣을 수 있어서 하위 타입이 될수 없단다… 맞는말이군..! (리스코프 치환 원칙에 어긋남)

 

이러한 경우 비한정적 와일드카드인 ? 를 사용하여 printList(List<?>) 이렇게 사용하면 List<String> 이나 List<Integer> 등 모든 타입이 List<?> 의 하위 타입이기 때문에 어떤 타입의 List라도 타입을 보존한 채 출력할 수 있다.

 

단, Object 에는 Object 의 하위 타입을 넣을 수 있지만 List<?> 에는 null 만 넣을 수 있다. List<?> 에 어떤 타입의 List가 올지 모르기 때문에 타입이 존재하는 값을 넣을 수 없기 때문이다.

 

 

한정적 와일드카드 (Bounded Wildcards)

extends 를 사용한 한정적 와일드카드 (Upper Bound Wildcards)

타입의 제한을 풀어줄 때 사용. 제네릭 타입들을 상위 제네릭 타입으로 묶어주는 것이라 할 수 있다.

public static void process(List<? extends Foo> list) { ... }

super 를 사용한 한정적 와일드카드 (Lower Bounded Wildcards)

타입을 제한할 때 사용. 유연성을 극대화하기 위해 지정된 타입의 상위 타입만 허용하도록 한다.

public static void addNumbers(List<? super Integer> list) { ... }

 

그러면 이제 왜 한정적 와일드카드를 사용해 API 유연성을 높이라 하는지..! 드디어 본론으로 가자!!!!!

 

 

이렇게 Stack 클래스에 public API를 정의하고 일련의 원소를 스텍에 넣는 메서드를 추가하였다.

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
    public void pushAll(Iterable<E> src) {
    for (E e : src)
        push(e);
    }
}

 

이 메서드는 깨끗이 컴파일 되지만 완벽하진 않다.

Strack<Number> numberStack = new Stack<>();
Iterable<Integer> intergers = ...;
numberStack.pushAll(integers);

 

Integer는 Number의 하위 타입이니 잘 동작해야 할 것 같지만..!! 매개변수화 타입이 불공변(invariant) 이기 때문에 다음의 오류 메시지가 뜬다

StackTest.java:7: error: incompatible types: Iterable<Integer>
cannot be converted to Iterable<Number>
    numberStack.pushAll(integers);
                        ^

 

이러한 상황에 대처하기 위한 한정적 와일드카드 를 사용한다!! pushAll 의 입력 매개변수 타입은 ‘E 의 Iteragle’ 이 아니라 ‘E의 하위 타입의 Iterable’ 이어야 한다.

( Iterable<? extends E>가 ‘E의 하위 타입의 Iterable’ 이라는 뜻이다.)

public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}

 

그러면 이번엔 아래와 같이 popAll 메소드가 추가되었다고 하자.

public void popAll(Collection<E> dst) {
    when (!isEmpty())
    dst.add(pop())
}

 

그리고 아래처럼 Stack<Number> 의 원소를 Object용 컬렉션으로 옮기려 한다고 하자. 오류가 날까?

Strack<Number> numberStack = new Stack<>();
Collection<Object> objects = ...;
numberStack.popAll(objects);

 

“Collection<Object>는 Collection<Number> 의 하위 타입이 아니다” 라는 오류가 뜬다..!

 

이번에도 한정적 와일드카드 타입으로 해결가능하다!

 

popAll의 입력 매개변수의 타입이 ‘E의 Collection’이 아니라 ‘E의 상위 타입의 Collection’ 이어야 한다.

(Collection<? super E>가 ‘E의 상위 타입의 Collection’ 이라는 뜻)

public void popAll(CoUection<? super E> dst) {
while ( ! isEmpty() )
dst.add(pop() ) ;
}

 

이제 Stack과 클라이언트 코드 모두 말끔히 컴파일된다.

 

 

결론❗❗❗

➡️ 유연성을 극대화 하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라!

그럼 어떤 와일드카드 타입을 써야하는지는?? 팩스(PECS) 를 기억하자!!

 

팩스(PECS)

producer-extends, consumer-super 즉, 매개변수화 타입 T가 생산자라면 <? extends T>를 사용하고, 소비자라면 <? super T>를 사용하라.

사실 이 Item31의 내용은 이 밑으로 더 있었지만.. 정리하는 내가 이해가 안된다 ㅎㅎㅎㅎ 이해안되면 적지를 못한다..! 그리고 지금 이 내용만으로도 왜 와일드카드 타입을 사용하여야 API가 유연해지는지 알 수 있을것같기에..!!

스킵!!ㅎㅎㅎㅎㅎㅎㅎㅎㅎ

 

 

핵심정리!!

조금 복잡하더라도 와일드카드 타입을 적용하면 API가 훨씬 유연해진다. 그러니 널리 쓰 일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용해줘야 한다. PECS 공식율 기억하자 즉, 생산자(producer)는 extends를 소비자(consumer)는 super를 사용한다.

 

 

 

 

 

참조


도서 - Effective Java

https://snoop-study.tistory.com/113

 

[Java] 와일드 카드 (Wildcards)

와일드 카드 와일드 카드는 제네릭 코드에서 물음표(?)로 표기되어 있는 것을 말하며, 아직 알려지지 않은 타입을 나타냅니다. 1) Bounded Wildcards (한정적 와일드카드) Upper Bounded Wildcards (extends를 사

snoop-study.tistory.com

 

 

 

댓글