Search

Item 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

생성일
2023/07/25 05:52
챕터
5장 - 제네릭

제네릭과 가변인수는 잘 호환되지 않는다

매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 이런 상황에서는 컴파일러가 자동 생성한 형변환이 실패할 수 있기 때문에, 타입 안전성이 깨진다.
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가 달려있기 때문이다.