[Java] 제네릭과 가변인자 조합 메서드의 @SafeVarargs 어노테이션
@SafeVarargs 어노테이션을 이해하기 위해선 가변인자와 제네릭의 조합을 알아야한다.
가변인자를 사용하면, 메서드를 호출할 때 매개변수의 개수를 자유롭게 넣을 수 있고 해당 값들이 담겨 있는 배열이 생성된다.
public static int sum(int... nums) {
return Arrays.stream(nums).sum();
}
- int... 로 받은 정수형 타입의 매개변수의 합을 반환하는 기본적인 예시
그렇다면 만약 제네릭 함수에서 가변인자를 사용하면 어떻게 될까?
아래에 List<T>를 여러개 받아 모두 하나의 List<T>로 합쳐주는 flatten 메서드가 있다.
public static <T> List<T> flatten(List<T>... lists) {
List<T> result = new ArrayList<>();
for (List<T> list : lists) {
result.addAll(list);
}
return result;
}
flatten 메서드에서는 타입 파라미터인 T와 가변인자가 함께 사용되었고 겉보기에 문제가 없고 실제로도 정상적으로 실행할 수 있다.
public static void main(String[] args) {
List<Integer> result = flatten(
Arrays.asList(1, 2),
Arrays.asList(3, 4, 5)
);
System.out.println(result); // 결과 1 2 3 4 5
}
하지만 main 메서드 실행 시 아래와 같이 warning이 발생한다.
java uses unchecked or unsafe operations.
배열은 상위 타입의 배열로 치환 될 수 있고, 상위 타입으로 치환된 배열에는 실제 타입과 다른 하위 타입이 들어갈 수 있다.
기존 flatten 메서드에 Object 배열로 추가하는 코드를 추가했다.
public static <T> List<T> flatten(List<T>... lists) {
Object[] obj = lists; // 타입 변질
obj[1] = 1;
List<T> result = new ArrayList<>();
for (List<T> list : lists) {
result.addAll(list);
}
return result;
}
위 예시에서의 핵심은 List<T>[] 타입인 lists를 Object[] 타입으로 형변환 하는 것이다.
Object[] 타입으로 형변환을 하게되면, Object 배열에는 Object의 하위 타입인 무엇이든 넣을 수 있고 넣으려는 값은 원래 배열에 있던 List<T>와는 다른 타입일 수 있고 따라서 lists가 변질될 수 있다.
심지어 이런 에러는 컴파일 시점이아닌 런타임 시점에만 확인이 가능하다.
그래서 자바는 제네릭 + 가변인자의 조합에 대해 경고한다.
제네릭과 가변인자를 함께 사용했을 때 시스템 상 문제가 발생할 허점이 남아있지만, flatten 메서드 처럼 문제가 생기지 않는 코드를 작성할 수도 있다.
당장 사용했던 Arrays.asList() 내부를 살펴보면 제네릭과 가변인자의 조합이고 실제로 @SafeVarargs 어노테이션이 붙어있다.
따라서 @SafeVarargs 어노테이션의 의미는 “메서드에서 사용하고 있는 제네릭 + 가변인자 조합은 안전하다” 라는 의미이다.
@SafeVarargs 어노테이션을 붙인다면 기존에 발생했던 warning도 제거되는 것을 확인할 수 있다.
참고
@SafeVarargs 어노테이션은 자바 7에서 등장했으며 public 메서드에만 적용할 수 있었는데
자바 9부터 private 함수에도 적용할 수 있게 되었다.