아이템 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라. - ksw6169/effective-java GitHub Wiki

제네릭과 가변인수 매개변수를 같이 쓰면 경고가 발생한다.

메소드를 선언할 때 실체화 불가 타입으로 varargs 매개변수를 선언하면 컴파일러가 경고를 보낸다. 또한 varargs 메소드를 호출할 때도 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[] 로 List[] 를 받음.
    Object[] objects = stringLists; 

    // stringLists(List[]) 의 첫 번째 요소로 타입이 다른 객체를 참조하여 힙 오염 발생
    objects[0] = intList;

    // 마지막에 컴파일러가 묵시적 형변환을 수행하여 ClassCastException 발생
    String s = stringLists[0].get(0);
}

위처럼 다른 타입 객체를 참조하는 상황이 생기면 힙 오염으로 인해 문제가 발생할 수 있다. 따라서 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다.


제네릭 varargs 매개변수를 받는 메소드를 선언 가능한 이유

제네릭 배열을 프로그래머가 직접 생성하는 것은 허용되지 않는다. 하지만 제네릭 varargs 매개변수를 받는 메소드는 선언 가능하다. 이유는 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메소드가 실무에서 유용하기 때문이다. 예시로 자바 라이브러리도 이런 류의 메소드를 여럿 제공한다.

Arrays.asList(T... a)

Collections.addAll(Collection<? super T> c, T... elements)

EnumSet.of(E first, E... rest)

이전 코드와 다른 점은 이 메소드들이 타입 안전하다는 점이다.


@SafeVarargs

  • 자바 7부터 도입된 어노테이션으로 제네릭 가변인수 메소드 작성자가 클라이언트 측에서 발생하는 경고를 숨길 수 있게 해주는 어노테이션이다.
  • 만약 메소드가 타입 안전함을 보장할 수 있다면 이 어노테이션을 사용해 경고를 제거할 수 있다.
  • 이 어노테이션은 재정의할 수 없는 메소드에만 달아야 한다. 재정의한 메소드가 안전한지 보장할 수 없기 때문이다. (자바 8에서는 정적 메소드와 final 메소드에만 달 수 있고, 자바 9부터는 private 인스턴스 메소드에도 허용된다.)
  • 참고로 자바 7 이전에는 @SuppressWarnings("unchecked") 를 달아 비검사 경고를 숨겼었다.

제네릭 가변인수 메소드가 타입 안전함을 어떻게 확인할 수 있을까?

1. 가변인수 메소드를 호출할 때 생성되는 제네릭 배열의 원소를 덮어쓰지 않는지 확인한다.

2. 제네릭 배열의 참조가 메소드 밖으로 노출되지 않는지 확인한다.
- 이 말은 즉, 신뢰할 수 없는 코드에 노출하면 안된다는 말이다. 여기서 신뢰할 수 없는 코드란 제네릭 가변인수 배열을 
  사용하면서 @SafeVarargs를 사용하지 않은 메소드(타입 안전성을 보장하지 못한 메소드) 등을 말한다.

정리하자면 가변인수 매개변수 배열이 호출자로부터 그 메소드로 순수하게 인수들을 전달하는 역할만 한다면
(varargs의 목적대로만 쓰인다면) 그 메소드는 안전하다.

꼭 제네릭 가변인수 배열을 사용하는 것만이 정답은 아니다.

타입 안전성을 보장함을 확인하고 @SafeVarargs 를 사용해 메소드를 선언하는 것이 꼭 정답만은 아니다. 제네릭 varargs 매개변수 대신 List로 대체해서 사용할 수도 있다.


참고 자료

  • Effective Java 3/E
⚠️ **GitHub.com Fallback** ⚠️