ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 26. 로 타입은 사용하지 말라
    Book/Effective Java 3E 2022. 11. 8. 22:00
    반응형

      클래스와 인터페이스 선언에 타입 매개변수(type parameter)가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 합니다.[JLS, 8.1.2, 9.1.2] 예컨대 List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받습니다. 그래서 이 인터페이스의 완전한 이름은 List<E>지만, 짧게 List라고도 자주 씁니다. 제네릭 클래스와 제네릭 인스턴스를 통틀어 제네릭 타입(generic type)이라 합니다.

     

      각각의 제네릭 타입은 일련의 매개변수화 타입(parameterized type)을 정의합니다.[JLS, 4.5] 예컨대 List<String>은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입입니다. 여기서 String이 정규(formal) 타입 매개변수 E에 해당하는 실제(actual) 타입 매개변수입니다.

     

      제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의됩니다. 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말합니다.[JLS, 4.8] 예컨대 List<E>의 로 타입은 List입니다.

     

      제네릭을 지원하기 전에는 컬렉션을 다음과 같이 선언했습니다. 자바 9에서도 여전히 동작하지만 좋은 예라고 볼 수는 없습니다.

    // 코드 26-1 컬렉션의 로 타입 - 따라 하지 말 것!
    private final Collection stamps = ...;

      이 코드를 사용하면 실수로 도장(Stamp) 대신 동전(Coin)을 넣어도 아무 오류 없이 컴파일되고 실행됩니다.

    stamps.add(new Coin(...)); // "unchecked call" 경고를 내뱉는다.

      컬렉션에서 이 동전을 다시 꺼내기 전에는 오류를 알아채지 못합니다.

    // 코드 26-2 반복자의 로 타입 - 따라 하지 말 것!
    for (Iterator i = stamps.iterator(); i.hasNext(); ) {
      Stamp stamp = (Stamp) i.next(); // ClassCastException을 던진다.
      stamp.cancel();
    }

     

      오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋습니다. 제네릭을 활용하면 컴파일러는 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장합니다.

    // 코드 26-3 매개변수화된 컬렉션 타입 - 타입 안정성 확보!
    private final Collection<Stamp> stamps = ...;

      로 타입을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 되기에 절대로 써서는 안 됩니다. 로 타입은 제네릭 없이 짠 기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와도 맞물려 돌아가게 하기 위한 마이그레이션 호환성 지원입니다.

    package effectivejava.chapter5.item26;
    import java.util.*;
    
    // 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용
    public class Raw {
        public static void main(String[] args) {
            List<String> strings = new ArrayList<>();
            unsafeAdd(strings, Integer.valueOf(42));
            String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다.
        }
    
        private static void unsafeAdd(List list, Object o) {
            list.add(o);
        }
    }

     

    비한정적 와일드카드 타입(unbounded wildcard type)

      제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표(?)를 사용하면 됩니다. 예컨대 제네릭 타입인 Set<E>의 비한정적 와일드카드 타입은 Set<?>입니다. 이것이 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입입니다. 로 타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽습니다(코드 26-4의 unsafeAdd 참조). 반면, Collection<?>에는 (null 외에는) 어떤 원소도 넣을 수 없습니다. 다음 코드는 비한정적 와일드카드 타입의 활용 예입니다.

    // 비한정적 와일드카드 타입을 사용하라. - 타입 안전하며 유연하다.
    // 타입에 상관없이 내부에 있는 값들을 출력한다.
    public static void unboundedWildcardType(List<?> list) {
      for (Object o : list) System.out.println(o);
    }

     

    로 타입을 쓰지 말라는 규칙의 예외

    • class 리터럴에는 로 타입을 써야 한다.
    • 런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 비한정적 와일드카드 타입의 꺾쇠괄호와 물음표는 아무런 역할 없이 코드만 지저분하게 만드므로, 차라리 로 타입을 쓰는 편이 깔끔하다.

     

    로 타입을 사용하면 런타임에 예외가 일어날 수 있으니 사용하면 안 된다.

    로 타입은 제네릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐이다.

    빠르게 훑어보자면, Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이고, Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다.

    그리고 이들의 로 타입인 Set은 제네릭 타입 시스템에 속하지 않는다.

    Set<Object>와 Set<?>는 안전하지만, 로 타입인 Set은 안전하지 않다.

     

    용어 정리

    한글 용어 영문 용어 Item
    매개변수화 타입 parameterized type List<String> 26
    실제 타입 매개변수 actual type parameter String 26
    제네릭 타입 generic type List<E> 26, 29
    정규 타입 매개변수 formal type parameter E 26
    비한정적 와일드카드 타입 unbounded wildcard type List<?> 26
    로 타입 raw type List 26
    한정적 타입 매개변수 bounded type parameter <E extends Number> 29
    재귀적 타입 한정 recursive type bound <T extends comparable<T>> 30
    한정적 와일드카드 타입 bounded wildcard type List<? extends Number> 31
    제네릭 메서드 generic method static <E> List<E> asList(E[] a) 30
    타입 토큰 type token String.class 33

     

    [참고 정보]

    이펙티브 자바 Effective Java 3/E 도서 [조슈아 블로크 ]

    이펙티브 자바 깃허브 저장소

    <이펙티브 자바, 3판> 번역 용어 해설

    반응형

    댓글

Designed by Tistory.