ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 26. 로 타입은 사용하지 말라
    백수의 개발/이펙티브 자바 2019. 10. 14. 12:32

    제네릭 타입

    클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다.

    제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 한다.

     

    제네릭 타입은 일련의 매개변수화 타입을 정의한다. 예컨데 List를 선언할 때 List<String>은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입이다. 여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수다.

    그러나, 로 타입List와 같이 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 제네릭이 도래하기 전 코드와의 호환성을 위해 남겨둔 것이다.

     

    로 타입의 문제

    private final Collection stamps= ...;
    
    stamps.add(new Coin(...));
    
    for(Iterator i = stamps.iterator(); i.hasNext();){
        Stamp stamp = (Stamp) i.next(); // ClassCastException을 던진다.
        stamp.cancel()
    }

    위와 같이 코드를 작성하게 되면 Collection에 Coin을 넣고, Stamp로 가지고 오려고 하면 ClassCastException의 런타임 에러가 발생한다.

    오류는 가능한 한 발생 즉시, 이상적으로는 컴파일 타임에 발견되는 것이 좋다.

    따라서, 위처럼 로 타입을 사용하지 말고 아래처럼 매개변수화된 타입을 사용해야 한다.

    private final Collection<Stamp> stamps= ...;
    
    stamps.add(new Coin(...)); // incompatible types: Coin cannot be converted to Stamp

    위와 같이 매개변수화된 타입을 사용하게되면, 컴파일 타임에 올바르지 않은 인스턴스를 넣으려 한 것에 대해 에러를 알려준다.

     

    이처럼 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다.

     

    이뿐만 아니라 함수의 매개변수에서도 로 타입을 사용하게 되면 문제가 발생한다.

    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);
    }

    전달하는 인자 strings는 List<String>으로 매개변수화된 타입이지만, unsafeAdd의 매개변수 list는 로 타입이다.

    위 코드 또한 컴파일은 되지만, 로 타입인 list로 부터 타입 안전하지 않은 인스턴스를 넣게 되고, get에서 ClassCastException이 발생된다.

     

    비한정적 와일드 카드

    위와 같이 타입을 몰라도 되는 로 타입을 쓰고 싶을 때가 있다. 아래 코드를 살펴보자

    static int numElementsInCommon(Set s1, Set s2){
        int result = 0;
        for(Object o1 : s1){
            if(s2.contains(o1)){
                result++;
            }
        }
        return result;
    }

    이 코드는 앞서 얘기한 로 타입의 매개변수를 사용한다. 이 또한 컴파일은 되지만 안전하지 않다. 따라서 이러한 경우 아래와 같이 비한정적 와일드카드 타입을 대신 사용하는 것이 좋다.

    static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}

    와일드카드 타입은 안전하고, 로 타입은 안전하지 않다. 로 타입은 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다. 반면 와일드카드에는 어떤 원소도 넣을 수 없다.

     

    로 타입 사용

    로 타입도 사용해야할 때가 있다.

    1. class 리터럴
      • List.class, String[].class, int.class는 허용하지만, List<String>.class, List<?>.class는 허용하지 않는다.
    2. instanceof 연산자
      • 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다.

    마무리

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

    Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이고, Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다. 그리도 이들의 로 티입인 Set은 제네릭 타입 시스템에 속하지 않는다. Set<Object>와 Set<?>는 안전하지만, Set은 안전하지 않다.

    댓글

Designed by Tistory.