-
이펙티브 자바 Item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라언어/Effective Java 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
'언어 > Effective Java' 카테고리의 다른 글
이펙티브 자바 Item 34 - int 상수 대신 열거 타입을 사용하라 (0) 2024.06.05 이펙티브 자바 Item 33 - 타입 안전 이종 컨테이너를 고려하라 (0) 2024.06.05 이펙티브 자바 Item31 한정적 와일드 카드를 사용해 API의 유연성을 높여라 (0) 2024.06.05 이펙티브 자바 Item 30 - 이왕이면 제네릭 메서드로 만들라 (0) 2024.06.04 이펙티브 자바 Item29 - 이왕이면 제네릭 타입을 사용하라 (0) 2024.06.04