언어/Effective Java
이펙티브 자바 Item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라
now0204
2024. 6. 5. 10:28
가변인수 ...은 메서드를 사용하는 클라이언트에서 파라미터를 몇개 보낼지 선택하는 것
아래와 같이 가변인수와 제네릭을 함께 사용하면 타입 안정성 문제가 발생한다.
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
}
- 제네릭은 성격상 배열과 궁합이 별로다.
- 제네릭 타입의 배열을 정의한는 걸 컴파일러가 막아준다.
- 그러나 가변인자는 내부적으로 제네릭의 배열이 만들어지는 경우가있다.
- 가변인자와 제네릭을 같이 사용하면 제네릭 타입의 배열이라고 부른다.
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
}
- List의 배열을 Object에 할당한다. 배열은 공변이므로, List의 배열을 Object에 할당할 수 있다.
- objects[0] = intList처럼 Object배열에 List<Integer>의 원소를 할당할 수 있다.
- stringLists[0].get(0)코드 실행시 에러가 발생하는데, 해당 코드는 컴파일 시 String 캐스팅 코드를 넣어주어야한다.
- stringLists[0]은 List<String>타입이기 때문이다.
- 이와 같은 경우 런타임에 타입 안정성이 깨지게된다. 따라서 제네릭을 사용한 가변인자를 권하지 않는다.
@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;
}
- @SageVarargs는 가변인자를 안전하게 사용하고 있다는 뜻이다.
가변인자를 안전하게 사용하는 방법
1. 제네릭 가변인자에 아무것도 넣지 않는다.
@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;
}
- 파라미터를 가공하지않고, 다시 제네릭 타입의 list에 넣고 있다. 아주 안전하다.
2. 제네릭 가변인자를 절대로 밖으로 노출하지 않는다.
static <T> T[] toArray(T... args) {
return args;
}
- 이런거 하지 말라는 것!!
- 제네릭 배열을 그대로 리턴하는데, 타입을 Object로 판단한다.
- 타입이 안정적이지 않을 수 있다! 이와 같은 상황에서는 @SafeVarargs를 사용하지마라
@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;
}
위 코드도 아래와 같이 수정하여, 타입 안정성을 노리자
public class FlattenWithList {
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;
}
- flatten은 값을 받아 어딘가에 넣어주는 프로듀서이기 때문에 <? extends T>를 사용한다.
- T와 T하위타입은 T타입에 들어갈 수 있다!
- 가변인자 대신 List를 사용하는것이 코드가 약간 복잡해질 수 있다는 단점이 있지만, 장점이 더 많다!
- 가변인자 대신 List 혹은 List<List<? extendsT>>를 사용하는 것이 더 좋은 방법이다.
참고자료
https://javabom.tistory.com/88
https://pro-dev.tistory.com/156