가변인자 : 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해준다.
varargs
매개변수에 제너릭이나 매개변수화 타입이 포함되면 컴파일 경고 발생비 구체화 타입(non-reifiable type) : 타입 소거자에 의해 컴파일 타임에 타입 정보가 사라지는 것(런타임에 구체화화지 않는 것) - 제너릭이 해당
import java.util.Arrays;
import java.util.List;
public class Item32 {
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 발생
}
public static void main(String[] args) {
List<String> list = Arrays.asList("1","2","3");
dangerous(list);
}
}
ArrayList
와 제네릭을 사용한 ArrayList<E>
모두 정상적으로 동작해야하는 것이다.List<String>
을 하나의 Object []
에 담게되면서 힙 오염의 위험성을 가지게 된다.Object []
에 다른 2개의 타입이 들어가게되어 런타임에 에러가 발생한다. ClassCaseException
을 던지게 되는것이다.@SafeVarargs
사용(item27) 에서 배운 @SuppressWarnings("unchecked")
이 있다.
따라서 자바 7부터 @SafeVarargs
어노테이션이 추가되었다.
@SafeVarargs
는 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치이다.@safeVarargs
을 달면 안된다!!!@SafeVarargs
를 사용할 수 있는 안전을 보장하는 조건에는 2가지가 있다.
1. 메서드가 제네릭 배열에 아무것도 저장하지 않는다(그 매개변수들을 덮어쓰지 않는다)
2. 배열의 참조가 밖으로 노출되지 않는다(신뢰할 수 없는 코드가 배열에 접근하지 못하게 한다.
import java.util.concurrent.ThreadLocalRandom;
public class Item32 {
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(); // 도달할 수 없다.
}
public static void main(String[] args) {
String[] attributes = pickTwo("좋은", "사람", "만나자 우리"); // ClassCastException 발생
}
}
- 코드에서 ThreadLocalRandom 을 사용한 이유 - 아래 사이트 참조
Random 대신 ThreadLocalRandom을 써야 하는 이유
toArray
메서드가 반환하는 배열의 타입(T[]
)은 컴파일 타임에 결정되므로 힙 오염의 위험성을 내재한다.pickTwo
메서드는 toArray
메서드를 호출하기 만하고 항상 Object[]
타입 배열을 반환할 뿐이다.ClassCastException
을 던진다.pickTwo
의 반환값을 attributes
에 저장하면서 String []
으로 형변환하는 코드를 컴파일러가 자동생성하는데 Object[]
는 String[]
의 하위 타입이 아니므로 형변환이 실패된다.toArray 로 부터 2단계나 떨어져 있지만 에러가 발생한다. 그만큼 힙오염의 전이는 굉장히 위험하다.
따라서 제네릭 varargs 매개변수 배열에 다른 메서드가 접근하도록 허용하면 안전하지 않다.
2가지 예외 사항이 있다.
1. @SafeVarargs
로 안전한 또 다른 varargs
메서드에 넘기는 것은 안전
2. 그저 이 배열 내용의 일부 함수를 호출만 하는(Varargs
를 받지 않는) 일반 메서드에 넘기는 것도 안전하다.
책의 코드32-3 과 같이 List<T> result
에 varargs
배열의 값들을 옮겨주어 반환하면 안전하게 사용할 수 있다.
추가로
@SafeVarargs
어노테이션은 재정의할 수 없는 메서드에만 달아야 한다.(재정의한 메서드도 안전할지는 보장할 수 없다.)
- 정적 메서도와 final 인스턴스 메서드(자바8)
- private 인스턴스 메서드(자바9)
varargs 매개변수를 List 매개변수로 대체
static <T> List<T> flatten(List<List<? extends T>> lists){
List<T> result = new ArrayList();
// result 에 담아 result 반환
}
audience = flatten(List.of(friends, romans, countrymen
다음과 같이 정적 팩터리 메서드인 List.of
을 활용하여 인자로 넘길 수 있다.
List.of
에는 이미 @SafeVarargs
가 달려있다.
마지막으로 2번 방식으로 위의 코드를 개선해보자
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class Item32 {
static <T> T[] toArray(T ... args){
return args;
}
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("좋은", "사람", "만나자 우리"); // 정상 실행
}
}
toArray
에서 반환하는 제너릭 배열을 pickTwo
에서 List.of
를 통해 제너릭 리스트로 변경한 후 List<T>
로 반환한다. 따라서 더이상 실행이 ClassCastException
을 던지지 않는다@SafeVarargs
어노테이션을 제공해주는데@SafeVarargs
가 적용된 가령 List.of
같은 메서드를 활용하자이펙티브 자바 - 조슈아 블로크