[Effective Java] 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라

2022. 10. 18. 23:20· BE/Java
목차
  1. 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라
  2. 불공변 방식보다 유연해지기
  3. PECS : Producer-Extends, Consumer-super
  4. 자바 7 이전 사용방법
  5.  
  6.  
  7. 타입 매개변수 vs 와일드카드
  8.  
  9.  
  10. 정리
반응형

EFFECTIVE JAVA(이펙티브 자바)

 

이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.


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

 

매개변수화 타입은 불공변이다. 즉, 서로 다른 타입 Type1과 Type2가 있을 때 List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아니다.

즉, List<String>은 List<Object>의 하위 타입이 아니라는 뜻

(List<Object>는 어떤 객체든 넣을 수 있지만, List<String>에는 문자열만 넣을 수 있기 때문)

불공변 방식보다 유연해지기

ex 1) Stack 클래스

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

위의 코드에 일련의 원소를 스택에 넣는 메서드를 추가해보자.

 

ex 2) 위의 Stack 클래스에 추가할 pushAll 메서드

public void pushAll(Iterable<E> src) {
    for (E e : src)
        push(e);
    }
}
  • 위의 메서드는 컴파일되지만, 아래의 코드일 경우 오류가 발생한다.

 

// Integer는 Number의 하위 타입이기에 잘 될 것이라고 생각되지만, 오류가 발생
Strack<Number> numberStack = new Stack<>();
Iterable<Integer> intergers = ...;
numberStack.pushAll(integers);
  • 이러한 경우에 대처할 수 있는 한정적 와일드카드 타입이라는 특별한 매개변수화 타입이 존재한다.
  • pushAll의 입력 매개변수 타입은 ‘E의 Iterable’이 아니라 ‘E의 하위 타입의 Iterable’이어야 하며, 와일드카드 타입 Iterable<? extends E>가 정확히 이런 뜻이다.

 

 

ex 3) 와일드카드 타입을 적용한 psuhAll 메서드

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

위의 소스로 Stack은 물론 이를 사용하는 클라이언트 코드도 정상적으로 컴파일 된다.

그렇다면, pushAll과 반대로 Stack 안의 모든 원소를 주어진 컬렉션으로 옮겨 담는 popAll 메서드를 추가해보자.

 

 

 

ex4) 반대로 Stack 안의 모든 원소를 주어진 컬렉션으로 옮겨 담는 popAll 메서드

public void popAll(Collection<E> dst) {
    when (!isEmpty())
				dst.add(pop())
}
  • 위의 경우에도, 주어진 컬렉션의 원소 타입이 스택의 원소 타입과 일치한다면 정상적으로 작동한다.
  • 하지만, 아래의 경우에도 오류가 발생한다.

 

// Collection는 Collection의 하위 타입이 아니라는 오류 발생
Stack<Number> numberStack = new Stack<>();
Iterable<Object> objects = ...;
numberStack.pushAll(objects);
  • 이 또한, 와일드카드 타입을 사용하면 해결할 수 있다.
  • popAll의 입력 매개변수의 타입이 ‘E의 상위 타입의 Collection이어야 한다’의 의미를 가진 Collection<? super E>를 사용하면 된다.

 

ex 5) 와일드카드 타입을 적용한 popAll 메서드

public void popAll(Collection<? super E> dst) {
    when (!isEmpty())
		    dst.add(pop())
}
  • 이렇듯, 유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하면 된다.
  • 한편, 입력 매개변수가 생산자와 소비자 역할을 동시에 하는 경우엔 와일드카드 타입을 써도 좋은 건 없다.

 

 

 

 

PECS : Producer-Extends, Consumer-super

  • 매개변수화 타입 T가 생산자(Producer)라면 <? extends T>를 사용하고, 소비자(Consumer)라면 <? super T>를 사용하라는 의미
  • 생성자의 반환타입에는 한정적 와일드카드 타입을 사용하면 안된다. (클라이언트코드에서도 와일드카드 타입을 써야하기 때문) 클래스 사용자가 와일드카드 타입을 신경써야 한다면 그 API에는 무슨 문제가 있을 가능성이 크다.

 

 

자바 7 이전 사용방법

  • 위의 코드들은 자바 8부터 정상적으로 컴파일되지만, 자바 7까지는 타입 추론 능력이 좋지 않아 문맥에 맞는 반환 타입(혹은 목표 타입)을 명시해야 한다.
  • 컴파일러가 올바른 타입을 추론하지 못하는 경우 명시적 타입 인수를 사용해서 타입을 알려주면 된다.

 

ex 6) 명시적 타입 인수를 사용한 예

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)

// 자바 7까지의 명시적 타입 인수 사용 
Set<Number> numbers = Union.<Number>union(integers, doubles);

 

 

타입 매개변수 vs 와일드카드

  • 타입 매개변수와 와일드카드에는 공통되는 부분이 있어 메서드를 정의할 때 둘 중 어느 것을 사용해도 괜찮을 때가 많다.
  • 예를 들어, 주어진 리스트에서 명시한 두 인덱스의 아이템들을 교환(Swap) 하는 정적 메서드를 두 방식 모두 정의해보자.

 

ex 7) 타입 매개변수와 와일드카드 방식을 이용한 swap 메서드

// 타입 매개변수 방식
public static <E> void swap(List<E> list, int i, int j);

// 와일드카드 방식
public static void swap(List<?> list, int i, int j);
  • 메서드 선언에 타입 매개 변수가 한 번만 나오면 와일드카드로 대체하는 것이 좋다.
    • 이 때 비한정적 타입 매개변수라면 비한정적 와일드 카드로 바꾸고, 한정적 타입 매개변수라면 한정적 와일드카드로 바꾸면 된다.
  • 위 코드에서 와일드카드 방식으로 구현한 코드가 컴파일 되지 않는다.
    • 원인 : 리스트의 타입이 List<?>인데 List<?>에는 null 외에 어떤 값도 넣을 수 없기 때문
    • 해결 : ex 8의 코드 처럼 private 도우미 메서드를 따로 작성하여 활용

 

ex 8) private 도우미 메서드 예

public static void swap(List<?> list, int i, int j) {
    swapHelper(i, list.set(j, list.get(i));
}

// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드
public static <E> void swapHelper(List<E> list, int i, int j) {
    list.set(i, list.set(j, list.get(i));
}
  • swapHelper 메서드는 리스트가 List<E> 임을 알고 있다.
  • 이 리스트에서 꺼내는 값의 타입은 항상 E이고, E타입의 값이라면 이 리스트에 넣어도 안전한 것이 확실하다.

 

 

정리

  • 복잡하지만, 와일드카드 타입을 적용하면 API가 훨씬 유연해질 수 있다.
  • PECS 공식을 기억하자 (생산자 : extends, 소비자 : super)
  • Comparable과 Comparator는 모두 소비자이다.
반응형

'BE > Java' 카테고리의 다른 글

[Effective Java] 33. 타입 안전 이종 컨테이너를 고려하라  (0) 2022.10.19
[Effective Java] 아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라  (1) 2022.10.18
[Effective Java] 아이템 30. 이왕이면 제네릭 메서드로 만들라  (1) 2022.10.13
[Effective Java] 아이템 29. 이왕이면 제네릭 타입으로 만들라  (0) 2022.10.12
[Effective Java] 아이템 28. 배열보다는 리스트를 사용하라  (0) 2022.10.11
  1. 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라
  2. 불공변 방식보다 유연해지기
  3. PECS : Producer-Extends, Consumer-super
  4. 자바 7 이전 사용방법
  5.  
  6.  
  7. 타입 매개변수 vs 와일드카드
  8.  
  9.  
  10. 정리
'BE/Java' 카테고리의 다른 글
  • [Effective Java] 33. 타입 안전 이종 컨테이너를 고려하라
  • [Effective Java] 아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라
  • [Effective Java] 아이템 30. 이왕이면 제네릭 메서드로 만들라
  • [Effective Java] 아이템 29. 이왕이면 제네릭 타입으로 만들라
멍목
멍목
개발 관련 새롭게 알게 된 지식이나 좋은 정보들을 메모하는 공간입니다.
반응형
멍목
김멍목의 개발블로그
멍목
전체
오늘
어제
  • 분류 전체보기 (514)
    • BE (190)
      • Spring (21)
      • Java (141)
      • Kotlin (6)
      • JPA (22)
    • FE (33)
      • Javascript (16)
      • Typescript (0)
      • React (5)
      • Vue.js (9)
      • JSP & JSTL (3)
    • DB (32)
      • Oracle (22)
      • MongoDB (10)
    • Algorithm (195)
    • Linux (8)
    • Git (6)
    • etc (42)
    • ---------------------------.. (0)
    • 회계 (4)
      • 전산회계 2급 (4)
    • 잡동사니 (2)

블로그 메뉴

  • 홈
  • 관리

공지사항

인기 글

태그

  • 자바 테스팅 프레임워크
  • junit5
  • MongoDB with Node.js
  • 이펙티브자바
  • Oracle
  • MongoDB 공부
  • 더 자바 Java 8
  • vue3 공부
  • MongoDB 기초부터 실무까지
  • 자기 개발
  • 알고리즘공부
  • JPA
  • 자바 개발자를 위한 코틀린 입문
  • 코테 공부
  • 자기공부
  • 자기 공부
  • 전산회계 2급 준비
  • 프로젝트로 배우는 Vue.js 3
  • 이펙티브 자바
  • JPA 공부
  • Effective Java
  • 코테공부
  • 코틀린
  • 더 자바 애플리케이션을 테스트하는 다양한 방법
  • 알고리즘 공부
  • Java to Kotlin
  • java 8
  • 자바 공부
  • 자기개발
  • 자바공부

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.0
멍목
[Effective Java] 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.