[Effective Java] 5 - item 32 제네릭과 가변인수를 함께 쓸 때는 신중하라.
item 32 제네릭과 가변인수를 함께 쓸 때는 신중하라.
- 가변인수: 메서드에 넘기는 인수의 개수를 클라이언트에서 조절이 가능하게 해 준다.
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList; // (1) 힙 오염 발생
String s = stringLists[0].get(0); // ClassCastException
}
- 제네릭 가변인수 배열 매개변수에 값을 저장하게 되면,
- 매개변수화 타입 변수가 타입이 다른 객체를 참조하게 되어 힙 오염이 발생할 수 있다. (1)
- 또한, 다른 타입 객체를 참조하는 상황에서 컴파일러의 자동 형변환이 실패할 수 있으므로 타입 안정성이 보장되지 않는다.
- 따라서 위 두 가지 이유로 인해 이 사용법은 권장되지 않는다.
@SafeVarargs
: 경고를 숨기자.
- 그러나 제네릭이나 파라미터로 가변 인자 매개변수를 받는 메서드가 실무에서는 매우 유용하기 때문에, 오류를 발생시키지는 않고 경고만 발생시킨다.
-
자바 라이브러리의 예시로는
Arrays.asList(T... a), Collections.addAll(Collection<? super T> c, T... elements), EnumSet.of(E first, E... rest)
등이 있다. 이들은 타입 안정성이 보장되어 있다. - 메서드 작성자 측에서 이 메서드가 타입 안정함을 보장할 수 있다면,
@SafeVarargs
어노테이션을 달아 클라이언트에서 발생할 경고를 숨길 수 있다. - 이 매개변수 배열이 순수하게 인수를 전달하는 일만 한다면, 메서드는 타입 안전하다.
제네릭 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다.
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
} throw new AssertionError(); // 도달할 수 없다.
}
public static void main(String[] args) {
String[] attributes = pickTwo("좋은", "빠른", "저렴한");
}
위 메서드는 아무런 문제가 없어 보이고 경고 없이 컴파일되나, 실행시에는 ClassCastException
을 발생시킨다. pickTwo()
의 반환 값을 attributes
에 저장하기 위해 매개변수화된 가변 인자를 String[]
으로 형변환하는 코드를 컴파일러가 자동 생성하기 때문이다.
이처럼 제네릭 매개변수 배열에 다른 메서드가 접근하면 안전하지 않으나, 두 가지 예외가 존재한다.
@SafeVarargs
처리한 또 다른 varargs 메서드에 넘기는 것은 안전하다.- 배열 내용을 호출만 하는 (varargs를 받지 않는) 일반 메서드에 넘기는 것도 안전하다.
varargs를 List
로 대체하기
@SafeVarargs
애너테이션이 유일한 정답은 아니며, varargs
매개변수를 List
매개변수로 바꿀 수도 있다.(item 28)
static <T> List<T> flatten(List<List<? extends T>> lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
audience = flatten(List.of(friends, romans, countrymen));
// List.of에도 @SafeVarargs 어노테이션이 달려 있어 이렇게 사용하는 것이 가능하다.
이 방식은 varargs 메서드를 안전하게 작성하는 게 불가능한 상황에서도 사용할 수 있다.
static <T> List<T> pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return List.of(a, b);
case 1: return List.of(a, c);
case 2: return List.of(b, c);
} throw new AssertionError();
}
public static void main(String[] args) {
List<String> attributes = pickTwo("좋은", "빠른", "저렴한");
}
// 배열 없이 제네릭만 사용하므로 타입 안전하다.
Leave a comment