Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
# 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높이라

## 핵심 정리

### 핵심 정리 1: Chooser와 Union API 개선

`PECS: Producer-Extends, Consumer-Super`

- `Producer-Extends`
- `Object의 컬렉션 Number나 Integer를 넣을 수 있다.`
- `Number의 컬렉션에 Integer를 넣을 수 있다.`
- 값을 받아서 저장하는 것은 Producer-Extends

```java
// 코드 31-1 와일드카드 타입을 사용하지 않은 pushAll 메서드 - 결함이 있다! (181쪽)
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}

// 코드 31-2 E 생산자(producer) 매개변수에 와일드카드 타입 적용 (182쪽)
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}

// 제네릭 Stack을 사용하는 맛보기 프로그램
public static void main(String[] args) {
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
numberStack.pushAll(integers);

Iterable<Double> doubles = Arrays.asList(3.1, 1.0, 4.0, 1.0, 5.0, 9.0);
numberStack.pushAll(doubles);

...

System.out.println(objects);
}
```

- <E extends Number>는 한정적 타입
- <? extends Number>는 한정적 와일드카드
- ?에 Number를 상속받은 어떠한 타입이던 들어갈 수 있다.
- <? extends E>와 같이 선언하면? E 타입을 상속받은 어떠한 타입이던 들어갈 수 있다.
- 유연하게 처리할 수 있다.
- `Consumer-Super`
- `Integer의 컬렉션의 객체를 꺼내서 Number의 컬렉션에 담을 수 있다.`
- `Number나 Integer의 컬렉션의 객체를 꺼내서 Object의 컬렉션에 담을 수 있다.`
- 값을 꺼내서 전달해주는 로직은 Consumer-Super

```java
// 코드 31-3 와일드카드 타입을 사용하지 않은 popAll 메서드 - 결함이 있다! (183쪽)
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}

// 코드 31-4 E 소비자(consumer) 매개변수에 와일드카드 타입 적용 (183쪽)
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}

// 제네릭 Stack을 사용하는 맛보기 프로그램
public static void main(String[] args) {
Stack<Number> numberStack = new Stack<>();
...

Collection<Object> objects = new ArrayList<>();
numberStack.popAll(objects);

System.out.println(objects);
}
```

### 핵심 정리 2: Comparator와 Comparable은 소비자

- `Comparable을 직접 구현하지 않고, 직접 구현한 다른 타입을 확장한 타입을 지원하려면 와일드카드가 필요하다.`
- `ScheduledFuture는 Comparable을 직접 구현하지 않았지만, 그 상위 타입 (Delayed)이 구현하고 있다.`

**Producer-Extends**

```java
// T 생산자 매개변수에 와일드카드 타입 적용 (184쪽)
public class Chooser<T> {
private final List<T> choiceList;
private final Random rnd = new Random();

// 코드 31-5 T 생산자 매개변수에 와일드카드 타입 적용 (184쪽)
public Chooser(Collection<? extends T> choices) {
choiceList = new ArrayList<>(choices);
}

public T choose() {
return choiceList.get(rnd.nextInt(choiceList.size()));
}

public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);
Chooser<Number> chooser = new Chooser<>(intList);
for (int i = 0; i < 10; i++) {
Number choice = chooser.choose();
System.out.println(choice);
}
}
}
```

```java
// 코드 30-2의 제네릭 union 메서드에 와일드카드 타입을 적용해 유연성을 높였다. (185-186쪽)
public class Union {
public static <E> Set<E> union(Set<? extends E> s1,
Set<? extends E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}

// 향상된 유연성을 확인해주는 맛보기 프로그램 (185쪽)
public static void main(String[] args) {
Set<Integer> integers = new HashSet<>();
integers.add(1);
integers.add(3);
integers.add(5);

Set<Double> doubles = new HashSet<>();
doubles.add(2.0);
doubles.add(4.0);
doubles.add(6.0);

Set<Number> numbers = union(integers, doubles);

// // 코드 31-6 자바 7까지는 명시적 타입 인수를 사용해야 한다. (186쪽)
// Set<Number> numbers = Union.<Number>union(integers, doubles);

System.out.println(numbers);
}
}
```

**Consumer-Super**

```java
public class IntegerBox extends Box<Integer> {}

public class Box<T extends Comparable<T>> implements Comparable<Box<T>> {}
```

```java
// 와일드카드 타입을 사용해 재귀적 타입 한정을 다듬었다. (187쪽)
public class RecursiveTypeBound {
public static <E extends Comparable<? super E>> E max(List<? extends E> list) {
if (list.isEmpty())
throw new IllegalArgumentException("빈 리스트");

E result = null;
for (E e : list)
if (result == null || e.compareTo(result) > 0)
result = e;

return result;
}

public static void main(String[] args) {
List<IntegerBox> list = new ArrayList<>();
list.add(new IntegerBox(10, "effective"));
list.add(new IntegerBox(2, "java"));

System.out.println(max(list));
}
}
```

- IntegerBox 타입은 Comparable를 구현하지 않았지만 상위 타입(Box)은 구현한 경우가 있을 수 있다.
- 값을 꺼내서 처리하는 로직이기 때문에 Consumer-Super

### 핵심 정리 3: 와일드카드 활용 팁

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

```java
// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드 (189쪽)
public class Swap {

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

// 와일드카드 타입을 실제 타입으로 바꿔주는 private 도우미 메서드
// private static <E> void swapHelper(List<E> list, int i, int j) {
// list.set(i, list.set(j, list.get(i)));
// }

public static void main(String[] args) {
// 첫 번째와 마지막 인수를 스왑한 후 결과 리스트를 출력한다.
List<String> argList = Arrays.asList(args);
swap(argList, 0, argList.size() - 1);
System.out.println(argList);
}
}
```

- `E`: 특정할 수 있는 타입
- 타입을 안다!
- `?`: 특정할 수 없는 임의의 타입
- 비한정적 와일드 카드
- 타입을 모른다!
- `주의!`
- `비한정적 와일드카드(?)로 정의한 타입에는 null을 제외한 아무것도 넣을 수 없다.`

## 완벽 공략

### 완벽 공략 44. Type Inference

- `타입을 추론하는 컴파일러의 기능`
- 타입 추론이란 어떤 타입을 쓸지 컴파일러가 알아내는 것
- 다이아몬드 연산자를 사용하면 컴파일러가 알아서 타입 추론를 해준다.
- `모든 인자의 가장 구체적인 공통 타입 (most specific type)`
- `제네릭 메서드와 타입 추론: 메서드 매개변수를 기반으로 타입 매개변수를 추론할 수 있다.`
- `제네릭 클래스 생성자를 호출할 때 다이아몬드 연산자 <>를 사용하면 타입을 추론한다.`
- `자바 컴파일러는 "타겟 타입"을 기반으로 호출하는 제네릭 메서드의 타입 매개볒수를 추론한다.`
- `자바 8에서 "타겟 타입"이 "메서드의 인자"까지 확장되면서 이전에 비해 타입 추론이 강화되었다.`

```java
public class BoxExample {

private static <U> void addBox(U u, List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}

private static <U> void outputBoxes(List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}

private static void processStringList(List<String> stringList) {

}

public static void main(String[] args) {
ArrayList<Box<Integer>> listOfIntegerBoxes = new ArrayList<>();
BoxExample.addBox(10, listOfIntegerBoxes);
BoxExample.addBox(20, listOfIntegerBoxes);
BoxExample.addBox(30, listOfIntegerBoxes);
BoxExample.outputBoxes(listOfIntegerBoxes);

// Target Type
List<String> stringlist = Collections.emptyList();
List<Integer> integerlist = Collections.emptyList();
BoxExample.processStringList(Collections.emptyList());
}
}
```

- `BoxExample.<Integer>addBox()`와 같이 사용할 수 있다.
- 이를 공식문서에서는 type witness(?) 라고한단다..
- java7에서는 위와 같이 선언이 필요하다.

# References

- 타입 추론: [https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html](https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html)
Loading