1. What: 제네릭에 가변인수를 쓰는 예시
2. Why: 가변인수와 제너릭을 함께쓰면 타입안정성이 보장되지 않는다.
static void dangerous(List<String>... stringLists) { // (1)
List<Integer> intList = List.of(42); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4) 힙 오염 발생
String s = stringLists[0].get(0); // (5) ClassCastException
}
가변인수 메서드를 호출하면 가변인수를 담기위한 배열이 자동으로 하나 만들어진다.
-> 위 예시에서는 List<String>[] stringLists이다.// (1)
이 배열이 Obejct[]에 저장될 수 있고 // (3)
objects[0]에 다른 타입인 intList가 들어간다. // (4)
마지막 줄에서 stringLists의 0번째 객체를 호출하면 ClassCastException이 발생한다. //(5)
-> 위 처럼 타입 안정성이 깨지기 때문에 제네릭에 가변인수를 쓰는 것은 안전하지 않다.
그렇다면 왜 허용하는걸까??
- 제네릭이나 매개변수화 타입(ex:List<String>)의 varagrs 매개변수를 받는 메서드가 실무에서 유용하기 때문이다.
- 예시!! (위 예시와 다르게 아래예시들은 타입 안전하다)
1. Arrays.asList(T... a) -> 간단하게 원하는 원소들로 리스트를 만들 수 있으니 유용
2. Collections.addAll(Collection<? super T> c, T...elements)
3. EnumSet.of(E first, E... rest)
3. How: (1) 메서드가 타입 안전함을 보장 (2) 가변인수 대신에 List로 생성
1) @SafeVarargs로 메서드 작성자가 그 메서드가 타입 안전함을 보장한다.
- 메서드 작성자가 메서드에 @SafeVarargs를 붙이면 클라이언트에게 경고문을 노출하지 않는다.
타입 안전한 경우
- 배열에 아무것도 저장하지 않고, 배열의 참조가 외부로 노출되지 않는 경우
- 인수를 전달하기만 하는 경우(가변인수의 목적)
ex) 타입 안전하지 않은 경우 - 제네릭 매개변수 배열의 참조를 노출한다.
static <T> T[] toArray(T... args) {
return args;
}
반환 타입은 메서드에 인수를 넘기는 컴파일 타임에 결정되는데, 그 시점에는 컴파일러에게 충분한 정보가 주어지지 않아 타입을 잘못 판단할 수 있다.
때문에 잘못된 반환정보로 메서드를 호출한 쪽에 힙오염을 시킬 수 있다.
@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;
}
1. varags 매개변수 배열에 아무것도 저장하지 않는다.
2. 그 배열(혹은 복사본)을 신뢰할 수 없는 코드에 노출시키지 않는다.
해당 메서드는 위 두 가지 조건을 만족하여 안전함을 보장한다. 안전한 varargs 메서드에는 @SafeVarargs 어노테이션을 달아서 컴파일러 경고를 없애는 것이 좋다.
2) 가변인수를 List로 변경하라
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;
}
정적 팩터리 메서드에 List.of를 활용하면 메서드에 임의의 갯수의 인수를 넘길 수 있다! (= 가변인수와 같은 역할을 수행한다.)
[장점]
직접 @SafeVarargs를 달지 않아도 된다. 즉 직접 타입 안정성을 검증할 필요가 없다. (List.of에 @SafeVarargs이 달려있다. = 컴파일러에서 타입안정성을 체크한다.)
[단점] 코드가 조금 지저분하고 속도가 약간 느려질 수 있다.
'Java' 카테고리의 다른 글
[Effective Java][아이템35] ordinal 메서드를 잘못사용한 사례와 해결방법 (0) | 2022.06.12 |
---|---|
[Effective Java][아이템34] ENUM 기본 사용법과 여러 구현 케이스 소개 (0) | 2022.06.12 |
[Effective Java] 아이템31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.05.25 |
[Effective Java] 아이템30. 이왕이면 제네릭 메서드로 만들라 (0) | 2022.05.22 |
[Effective Java] 아이템29. 이왕이면 제네릭 타입으로 만들라 (0) | 2022.05.22 |