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

yeom yaloo·2023년 12월 27일
0

Effective Java

목록 보기
19/20
post-thumbnail

제네릭

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

[핵심 정리]

[매개변수화 타입은 불공변이다.]

1. 불공변?

  • List<Type1>와 List<Type2>는 서로 상위 타입도 하위 타입도 아니다.
  • 이는 List<Object>와 List<String>을 생각하면 편한데 후자의 경우 전자의 기능을 모두 다할수 없다. String 타입만 담을 수 있기 때문이다.

[때론 불공변보다 더 유연한 무엇인가 필요하다]

1. stack의 메서드를 살펴보자

public class Stack<E>{
	// ... 생략
    
    public void pushAll(Iterable<E> src){
    	for (E e : src)
       		push(e);
    }
	
}
  • stack을 Number타입을 이용해서 선언한다고 pushAll의 매개변수로 Iterable<Integer>를 넣는다고 가정해보자.
  • Integer은 Number의 하위 타입으로 잘 동작한다고 생각이 되지만 실제는 오류가 난다.
  • 이는 매개변수화 타입이 불공변이기 때문이다.
  • Stack 클래스에서 매개변수화 타입을 Number로 줬다면 사용하는 모든 메서드 반환하는 값들은 모두 같이 맞춰주어야 한다.

2. 불공변의 한계를 해결하는 방법 - 한정적 와일드카드 타입

2-1. 한정적 와일드카드 타입?

제네릭의 제약 방법은 3가지가 존재한다.

  • ?: 와일드 카드로의 줄임 표현으로 모든 타입이 가능하다는 의미이다.
  • extends T: 상한 경계를 두는 표현으로 T와 T의 자손만 사용 가능합니다.
  • super T: 하한 경계를 두는 표현으로 T와 T의 부모만 사용 가능합니다.

2-2. 한정적 와일드카드 타입을 사용한 불공변의 한계를 해결하는 방법 (1)

public void pushAll(Iterable<? extend E> src){
	for (E e : src)
    	push(e);
}
  • Iterable<? extend E> src 한정적 와일드 카드 타입을 이용해서 E의 하위 타입의 Iterable이라고 명시해주고 있다.
  • 위와 같이 명시해주면 E의 Iterable이 아니고 E의 하위 타입의 Iterable이라는 뜻이 된다.
  • E는 어떤 타입이 들어와도 상관 없으며 해당 타입과 해당 타입의 하위 클래스를 모두 허용한다는 의미다.

[와일드카드를 사용해야할 때와 사용하지 않아야 할때 - PECS]

  • PECS
  • 메서드 선언에 타입 매개변수가 한 번만 나오는 경우

1. 와일드카드를 사용하는 이유

  • 유연성을 극대화하고 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용해라.

2. 사용하지 않아야 할 때

  • 입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드카드 타입을 사용해도 좋을 것이 없다.

3. PECS: Producer-extends / Consumer-super

  • 매개변수화 타입 T가 생산자인 경우
    • <? extendss T>를 사용하라
      -매개변수화 타입 T가 소비자인 경우
    • <? super T>를 사용하라

4. 생산자 매개변수에 와일드카드 타입 적용 코드

public void pushAll(Iterable<? extends E> src){
	for (E e: c)
    	push(e);
}
  • src가 인스턴스 E 생성을 위해서 사용되고 있기 때문에 이 경우라면 producer-extends를 사용해 와일드카드를 표현해야 한다.

5. 소비자 매개변수에 와일드카드 타입 적용 코드

public void popAll(Collections<? super E> dst){
	while(!isEmpty())
    	dst.add(pop())
}
  • 매개변수화 타입 dst가 stack에 있는 값을 소비하고 있기 때문에 소비자 매개변수에 와일드카드 타입을 적용해야 한다.

[와일드카드를 사용해야할 때와 사용하지 않아야 할때 - 메서드 선언에 타입 매개변수가 한 번만 나오는 경우]

1. 메서드 선언에 타입 매개변수가 한 번만 나오면 와일드카드로 대체하라.

public static void swap(List<?> list, int i, int j){
	list.set(i, list.set(j, list.get(i)));
}
  • 해당 문제는 컴파일 시 꺼낸 원소를 다시 넣을 때 문제가 생긴다.
  • 이 문제를 해결하기 위해서 private 도움 메서드로 따로 작성해 활용하는 방법이 있다.

1-1. 한 번만 나온 타입 매개변수를 오류 없이 사용하기 위한 방법

public static void swap(List<?> list, int i, int j){
	swapHelper(list, i, j);
}
  • 해당 메서드는 타입 매개변수가 한 번만 사용된 메서드로 와일드카드 타입의 실제 타입을 설정해주기 위해서 swapHelper()이 이를 대신하고 있다.

1-2. helper method

private static <E> void swapHelper(List<E> list, int i, int j){
	list.set(i, list.set(j, list.get(i)));
}
  • 와일드카드 타입의 실제 타입을 알려주는 메서드이다.
  • 실제 타입을 알아내기 위해서 이 도우미 메서드는 제네릭 메서드여야 한다.
profile
즐겁고 괴로운 개발😎

0개의 댓글