아이템 31. 한정적 와일드 카드를 사용해 API 유연성을 높여라
매개변수화 타입의 문제점
불공변이라 유연하지 않다.
복습
매개변수화 타입은 불공변(invariant)이다.
즉, "Type2가 Type1의 하위타입"은 List<Type2>가 List<Type1>의 하위 타입임을 보장하진 않는다.
왜 하위 타입임을 보장할 수 없을까
예를 들어, List<Object>와 List<String>을 살펴보자.
List<Object>에는 어떤 객체든지 넣을 수 있으나, List<String>에는 String만 넣을 수 있다, 즉, 리스코프 치환 원칙에 어긋나므로, List<String>은 List<Object>의 하위타입이 될 수 없다.
불공변의 예시
// 예시
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
위 class에 pushAll을 추가한다고 가정하자.
public void pushAll(Iterable<E> src) {
for (E e: src)
push(e);
}
Stack<Number>에 Integer 타입을 넣을 수 있을까?
답: 불가능하다.
불공변에 유연하게 대응할 수 없을까
"한정적 와일드 카드 타입"이라는 매개변수 타입을 사용하자.
// 생산자(producer) 매개변수에 와일드 타입 적용
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
// 소비자(consumer) 매개변수에 와일드 타입 적용
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
** 예제에서 언급된 개념
- 생산자(producer): 컬렉션을 살펴보고 각 항목으로 작업을 수행
- 소비자(Consumer): 컬렉션에 항목을 추가할 경우
PESCS
- 와일드카드 타입을 사용하는 기본 원칙
- 언제 Extends와 Super를 사용할지 알려주는 공식
producer-extends, consumer-super
결론
유연성을 극대화하려면, 원소의 생산자/소비자용 입력 매개변수에 와일드 타입을 사용하자.
질문
입력 매개변수가 생산자와 소비자 역할을 동시에 한다면 와일드 타입을 써도 좋을게 없다. (183p)
-> 질문: 동시에 한다는 건 어떤 예시가 있을까? 왜 좋을게 없을까?
-> 답변: comparable이 예시로 나옴. 자세히 보기!!
레퍼런스
- 생산자 매개변수와 소비자 매개변수의 개념
아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라.
가변인수(Variadic Argument)란?
- 정의
- Method의 Argument의 개수를 클라이언트가 조절 가능하다.
- 코드 예시
-
void mergeAll(List<String>... stringLists) {}
-
- 주의사항
- 반드시 한 개의 가변 인수만을 사용하자.
- 맨 마지막 Argument로 사용하자.
Heap Pollution(힙오염)
매개변수화 타입의 변수가 타입이 다른 객체를 참조하는 경우
-> 타입 안정성이 깨지므로, 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다.
안전하지 않다면, 왜 varargs 매개변수를 받는 메서드를 선언할 수 있게 했을까?
실무에서 유용하게 쓰여서, 자바 언어 설계자는 이를 수용했다.
제네릭과 가변인수, 어떻게 안전하게 쓸까?
- 제네릭 배열에 아무것도 저장하거나 덮어쓰지 말자.
- 배열의 참조를 밖으로 노출시키지 말것
'제네릭 가변인수 메서드 경고' 숨기기
언제 숨길까?
제네릭 가변인수 메서드를 클라이언트 단에서 사용시, heap pollution이 일어날 수 있다고 IDE가 경고한다.
'제네릭과 가변인수, 어떻게 안전하게 쓸까?'에서 언급한 2가지 조건을 모두 만족한다면, 경고를 제거 하는 것이 좋다.
어떻게 숨길까?
제네릭이나 매개변수화 타입의 varagrs 매개변수를 받는 모든 메서드에 @SafeVarargs를 달자.
@SafeVarargs
static <T> List<T> flatten(List<? extends T> ...lists) {
// 코드 생략
}
@SafeVarargs 없이 경고 없애는 방법
varargs의 매개변수를 List 매개변수로 바꿀 수 있다.
static <T> List<T> flatten(List<List>? extends T>> lists) {
// 코드 생략
}
단 위 코드는 클라이언트 코드가 조금 지저분해진다는 단점이 있다.
// 클라이언트 코드에서 List.of를 사용해야한다.
audience = flatten(List.of(friends, romans, countrymen));
정리
1. 제네릭 배열에 아무것도 저장하거나 덮어쓰지 말고, 배열의 참조를 밖으로 노출시키지 말아야한다.
2. 제네릭과 가변인수는 궁합이 잘 맞지 않으니, 함께 사용할 경우 조심하자
3. 이중 리스트도 해답이 될 수 있다.
레퍼런스
힙오염이란?
https://parkadd.tistory.com/130
아이템 33. 타입 안전 이종 컨테이너를 고려하라.
아이템 34. int 상수 대신 열거 타입을 사용하라.
정수 열거패턴
- 언제 사용했나: 열거타입의 등장 이전 사용.
- 단점
- 타입 안전 보장하지 않는다
- 표현력이 나쁘다 (prefix 붙여야함)
열거타입이란
- 일정 개수의 상수값을 정의, 그 외의 타입은 허용하지 않는 타입.
- 열거타입은 클래스이며, 상수 하나당 자신의 인스턴스 하나를 만들어 public static final 필드로 공개.
열거타입의 장점
- 컴파일타임 타입 안전성을 제공.
- 이름이 같은 상수도 공존 가능
- 새로운 상수 추가/순서 변경시, 재컴파일 필요 없다.
- toString()을 유용하게 사용 가능.
- 상수 제거시, 컴파일/런타임 오류 클라이언트에서 발생
열거타입 내 메소드 추가
방법 1. switch문 사용
- 좋지 않다. 메서드 추가 잊을 경우 runtime 에러 발생
방법 2. 상수별로 메서드 구현
코드 34-5, 코드 34-6
열거타입의 메서드 내 중복 코드 어떻게 제거할까
전략 열거 타입을 이용하자 (코드 45-9) : 각 요일 별로 PayType을 선택한다.
아이템 35. ordinal 메서드 대신 인스턴스 필드를 사용하라.
Ordinal 메서드란?
열거타입에서 몇 번째 위치인지 반환하는 메서드
public enum Ensemble {
Solo, DUET, TRIO
public int numberOfMusicians() { return ordinal() + 1; }
}
Ordinal 사용의 문제점
1. 동일한 정수를 사용하는 상수는 사용할 수 없다.
: 만약 2명이 연주하는 다른 연주 방식이 있다면? 이를 상수화할 방법이 없다.
2. 중간 값을 비울 수 없다.
: TRIO는 3명이 연주한다. 위 코드에서 5명이 연주하는 QUINTET를 추가시 4명에 대한 상수는 빈 값으로 둘 수 없다.
해결책
열거타입 상수에 연결된 값은 ordinal 메서드로 얻지 말고, 인스턴스 필드에 추가하자.
public enum Ensemble {
Solo(1), DUET(2), TRIO(3)
private final int numberOfMusicians;
Ensemble(int size) {this.numberOfMusicians = size;}
public int numberOfMusicians() { return ordinal() + 1; }
}
'개발 > [스터디] 이펙티브 자바' 카테고리의 다른 글
[이펙티브자바]아이템 52-56 (0) | 2022.02.13 |
---|---|
[이펙티브 자바 스터디] 아이템 41-46 (0) | 2022.01.23 |
[이펙티브자바]아이템28. 배열보다는 리스트를 사용하여라. (0) | 2022.01.02 |
[이펙티브자바]아이템27. 비검사 경고를 제거하라 (0) | 2022.01.02 |
[이펙티브자바]아이템26. 로타입은 사용하지 말라 (0) | 2022.01.01 |