아이템 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라.
JAVA ·가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해주는데, 구현 방식에 허점이 있다.
가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다.
그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다.
그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다.
제네릭과 가변인수를 함께 쓸 때는 신중하라
- 제네릭 가변인수 배열에 값을 저장하는 것은 안전하지 않다.
- 타입 안정성이 깨진다.
- eg)
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 }
- 자바7에 추가된 @SafeVarargs 애노테이션을 사용할 수 있다.
- 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치
- 컴파일 경고가 발생하지 않는다.
- @SuppressWarning(“unchecked”)를 사용하면 진짜 문제를 알려주는 경고마저 숨길 수 있다.
-
@SafeVarargs를 달 수 있는 경우
- varargs 매개변수 배열에 아무것도 저장하지 않는 경우
- 그 배열(혹은 복제본)을 신뢰할 수 없는 코드에 노출하지 않는 경우
- 재정의할 수 없는 메서드에만 달아야 한다.
- 재정의한 메서드도 안전할지는 보장할 수 없기 때문
- 자바 8에서는 오직 정적 메서드와 final 인스턴스 메서드에만 붙일 수 있고,
자바 9부터는 private 인스턴스 메서드에도 허용된다.
- 오류를 내지만 경고로 끝내는 이유는? 실무에서 매우 유용하기 때문
- 제네릭 가변인수 배열의 참조를 밖으로 노출하면 힙 오염을 전달할 수 있다.
- eg) static < T > T[] toArray(T… args) { return args; }
- 제네릭 varargs 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다.
- 예외적으로, @SafeVarargs를 사용한 메서드에 넘기는 것은 안전하다.
- 예외적으로, 배열의 내용의 일부 함수를 호출하는 일반 메서드로 넘기는 것은 안전하다.
- 아이템 28의 조언에 따라 가변인수를 List로 바꾼다면 (Best practice)
- 배열없이 제네릭만 사용하므로 컴파일러가 타입 안정성을 보장할 수 있다.
- @SafeVarargs 애노테이션을 사용할 필요가 없다.
- 실수로 안전하다고 판단할 걱정도 없다.