아이템 53에 나왔는데 왜 제네릭이랑 같이 묶여서 아이템 32에 나오는가...
아마도 Java 5 에 같이 추가가 되어 그런듯 하다.
...
를 타입 뒤에 붙여 해당 인자가 가변 인자임을 나타내서 정의한다.@SafeVarargs
@SuppressWarnings("varargs")
static <E> List<E> of(E... elements) {
switch (elements.length) { // implicit null check of elements
case 0:
return ImmutableCollections.emptyList();
case 1:
return new ImmutableCollections.List12<>(elements[0]);
case 2:
return new ImmutableCollections.List12<>(elements[0], elements[1]);
default:
return new ImmutableCollections.ListN<>(elements);
}
}
가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다.
이 가변인수 메서드를 호출할 때도 varargs 매개변수가 실체화 불가 타입으로 추론되면, 그 호출에 대해서도 경고를 낸다.
warning : [unchecked] Possible heap pollution from
parameterized vararg type List<String>
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<String>
배열을 받아서 objects 배열을 초기화 시켜주고List<Integer>
타입의 변수로 초기화 시켜줄때@SuppressWarnings("unchecked")
으로 하면 될까?Array.asList(T... a)
Collections.addAll(Collection<? super T> c, T... elements)
EnumSet.of(E first, E... rest)
@SafeVarargs
annotation은 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치이다.static <T> T[] toArray(T... args) {
return args;
}
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(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError();
}
toArray
메서드를 호출한다는 점만 빼면 안전하다.toArray
에 넘길 T 인스턴스 2개를 담을 varargs 매개변수 배열을 만드는 코드를 생성한다.pickTwo
에 어떤 타입의 객체를 넘기더라도 담을 수 있는 가장 구체적인 타입인 object[]
이다.public static void main (String[] args) {
String[] attributes = pickTwo("좋은", "빠른", "저렴한");
}
pickTwo
함수는 무조건 Object[]
을 반환하지만 main 함수에서는 String[]
으로 받기 때문이다.그래서 결과적으로 제네릭 varargs 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다는 것이다.
@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. varargs 매개변수 배열에 아무것도 저장하지 않는다.
2. 그 배열(혹은 복사본)을 신뢰할 수 없는 코드에 노출하지 않는다.
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;
}
아이템 28에 따라 배열말고 리스트를 사용하여 안전하게 바꾼 모습이다.
이와 같은 방식으로 앞서 설명했던 toArray
를 대체하여 안전하게 pickTwo
메서드를 재정의할 수 있다.
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(a, c);
case 2: return List.of(b, c);
}
throw new AssertionError();
}
그리고 클라이언트 코드는 아래와 같이 바뀐다.
public static void main (String[] args) {
List<String> attributes = pickTwo("좋은", "빠른", "저렴한");
}