관리 메뉴

루시와 프로그래밍 이야기

ITEM32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 본문

스터디/이펙티브 자바

ITEM32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

Lucy_Ko 2022. 10. 13. 00:56

※가변인수란? https://sleepyeyes.tistory.com/29

JDK1.5부터 매개변수의 개수를 동적으로 지정해 줄 수 있게 되었는데 이 기능을 가변인자(variable argument)라고 한다.

가변인자는 내부적으로 배열을 생성해서 사용한다! 

가변인수 메서드

  • 가변인수(varargs) 메서드와 제네릭은 함께 사용이 어렵다.
  • 가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해준다.
  • 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다.
  • 그런데 내부로 감춰야 했을 이 배열을 클라이언트에 노출시키는 문제가 생겼다.
  • 그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다.
static void dangerous(List<String>... stringLists) {
    List<Integer> intList = List.of(2);
    Object[] objects = stringLists;
    objects[0] = intList; // 힙 오염 
    /* stringList 는 Integer 타입으로 변경됨 */
    String s = stringLists[0].get(0); // ClassCastException 발생
}

@SafeVaragrs

  • 메서드 작성자가 이 메서드가 '타입안전함'을 보장하는 장치.
  • 메서드가 안전한 게 확실하지 않다면 절대 어노테이션을 달아서는 안된다.
  • 매개변수 배열이 순수하게 '인수들을 전달하는 일'만 한다면 그 메서드는 안전하다.

제네릭 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;
}

flatten 메서드는 임의 개수 리스트를 인수도 받아, 받은 순서대로 그 안의 모든 원소를 하나의 리스트로 옮겨 담아 반환한다. 이 메서드는 제네릭 varargs 매개변수를 안전하게 사용하는 예다. 따라서 @SafeVarargs 어노테이션도 달아야한다.

  • 규칙 : 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 모든 메서드에 @SafeVarargs를 달아라.
  • 그래야 사용자를 헷갈리게 하는 컴파일러 경고를 없앨 수 있다.
  • 이 말은 안전하지않은 varargs 메서드는 절대 작성해서는 안된다는 뜻이기도 하다.
  • 재정의할 수 없는 메서드에만 달아야 한다. 재정의할때 안전을 보장할 수 없기 때문이다.

 


정리

  • 가변인수와 제네릭은 궁합이 좋지 않다.
  • 가변인수 기능은 배열을 노출하여 추상화가 완벽하지 못하고,
  • 배열과 제네릭의 타입 규칙이 서로 다르기 때문이다.
  • 제네릭 varargs 매개변수는 타입 안전하지는 않지만, 허용된다.
  • 메서드에 제네릭 가변인수변수를 사용하고자 한다면 먼저 그 메서드가 타입안전인지 확인한 다음 @SafeVarargs를 달아라.
Comments