가변인수: 자바 5 때 추가되었고 메서드에 넘기는 인수(매개변수)의 개수를 클라이언트가 동적으로 조절할 수 있게 해주는 기능이다.
[제네릭과 가변인사를 혼용하면 타입안정성을 해칠 수 있다.]
...
final List<Integer> integers = List.of(42);
Object[] objects = stringLists;
objects[0] = integers; // 힙 오염 발생
String s = stringLists[0].get(0); // 형변환 예외(ClassCastException) 발생
...
warning: [unchecked] Possible heap pollution from
parameterized vararg type List<String>
매개변수에 제네릭이나 매개변수화 타입이 포함되면서 컴파일 경고가 발생하게 된다. 경고의 원인은 매개변수화 타입의 변수가 타입이 다른 객체를 참조하면서 힙 오염이 발생하기 때문이다.
매개변수화 타입이 변수가 타입이 다른 객체를 참조하면 입 오염이 발생한다. 이렇게 다른 객체를 참조하는 상황에서는 컴파일러가 자동생성한 형변환이 실패하여 제네릭 타입 안정성이 보장되지 못한다.
제네릭 배열을 프로그래머가 직접 생성하는건 허용하지 않으면서 제네릭 가변인자 매개변수를 받는 메서드를 선언할 수 있는건 분명 모순적이다.
이런 모순을 수용한 이유는 제네릭이나 매개변수화 타입의 가변인자 매개변수를 받는 메서드가 실무에서 유용하기 때문이다.
대표적인 예시로 Arrays.asList(T... a)
, Collections.addAll(Collection<? super T> c, T... elements)
, EnumSet.of(E first, E...rest)
가 있다.
메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치로 자바7에서 추가되었다.
[제네릭 가변인자(varargs) 매개변수 배열에 다른 메서드가 접근하면 안전하지 않다 - 힙 오염 발생, 콜스택까지 전이]
static <T> T[] pickTwo(T a, T b, T c){
switch (ThreadLocalRandom.current().nextInt(3)){
case 0: return toArray(a,b);
case 1: return toArray(b,c);
case 2: return toArray(a,c);
}
throw new AssertionError(); // 도달 못함.
}
static <T> T[] toArray(T... args){
return args;
}
...
public static void main(String[] args) {
final String[] attributes = pickTwo("좋은", "빠른", "저렴한"); // ClassCastException 발생
}
pickTwo
메서드는 어떤 타입의 객체를 넘기더라도 담을 수 있는 가장 구체적인 타입의 Object타입의 배열을 반환한다.
컴파일러가 String
배열로 형변환하는 코드를 자동으로 생성한다.
String
배열이 Object
배열의 하위 타입이 아니기 때문에 이 형변환은 ClassCastException
이 발생하며 실패한다.
[가변인자 매개변수 배열에 다른 메서드가 접근하고도 안전한 예외경우]
@SafeVarargs
static <T>List<T> flatten(List<? extends T>... lists){
final List<T> result = new ArrayList<>();
for (List<? extends T> list : lists){
result.addAll(list);
}
return result;
}
@SafeVarargs
로 메서드의 타입 안전함을 보장하고 있다.
@SafeVarargs
애너테이션은 재정의할 수 없는 메서드에만 달아야한다. 재정의한 메서드는 타입 안전성을 보장할 수 없기 때문이다. 자바 8에서는 정적 그리고final
인스턴스 메서드에만 붙일 수 있었고, 자바 9 부터는private
인스턴스 메서드에도 허용된다.
가변인자(varargs) 매개변수를 List 매개변수로 바꿀수도 있다.
[제네릭 varargs 매개변수를 List로 대체한 경우 - 타입안전]
static <T> List<T> pickTwo(T a, T b, T c){
switch (ThreadLocalRandom.current().nextInt(3)){
case 0: return List.of(a,b);
case 1: return List.of(b,c);
case 2: return List.of(a,c);
}
throw new AssertionError(); // 도달 못함.
}
...
public static void main(String[] args) {
final List<String> attributes = pickTwo("좋은", "빠른", "저렴한");
}
List.of
에도 @SafeVarargs
에너테이션이 달려있어 컴파일러가 메서드의 타입안전성을 검증하고 보장한다.메서드에 제네릭 가변인자(varargs) 매개변수를 사용하고자 한다면 메서드의 타입 안전성을 확인하고 @SafeVarargs
에너테이션을 사용하자.