제네릭과 가변인수는 잘 호환되지 않는다
•
매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 이런 상황에서는 컴파일러가 자동 생성한 형변환이 실패할 수 있기 때문에, 타입 안전성이 깨진다.
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList; // 힙 오염 발생
String s = stringLists[0].get(0); // ClassCastException 발생
}
Java
복사
•
이처럼 타입 안전성이 깨지기 때문에, 제네릭 가변인수 배열에 값을 저장하는 것은 안전하지 않다.
•
이와 같은 가변인수를 제네릭과 같이 사용하고 싶다면, @SuppressWarning(”unchecked”) 애노테이션을 하나하나 달아 경고를 숨겨주어야한다. 자바 7부터는 작성자가 해당 메서드가 타입 안전함을 보장하는 @SafeVarargs 애노테이션이 추가되어, 제네릭 가변인수 메서드를 작성할 때 경고를 숨길 수 있다.
static <T> T[] toArray(T... args) {
return args;
}
Java
복사
•
이와 같이 자신의 제네릭 매개변수 배열의 참조를 노출하는 것은, 컴파일 타임에 해당 배열의 타입을 결정하기 때문에 타입을 잘못 판단해 힙 오염이 발생할 수 있다.
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
Java
복사
•
따라서 이와 같이 사용해야 제네릭 가변인수 매개변수를 타입 안전하게 사용할 수 있다.
•
구체적으로 제네릭 varargs 메서드를 안전하게 사용하려면 다음 두 가지 조건을 지켜야 한다.
◦
varargs 매개변수 배열에 아무것도 저장하지 않는다.
◦
varargs 매개변수 배열이나 그 복제본을 신뢰할 수 없는 코드에 노출하지 않는다.
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;
}
Java
복사
•
이와 같이 가변인자 매개변수를 List 매개변수로 바꾸어 사용해도 타입 안전하게 사용할 수 있다.
audience = flatten(List.of(friends, romans, countrymen));
Java
복사
•
이처럼 List.of를 통해 해당 매개변수에 값을 넣을 수 있는데, 이게 가능한 이유도 List.of에 @SafeVarargs가 달려있기 때문이다.