[이펙티브 자바] 아이템 26. 로 타입은 사용하지 말라

June·2022년 3월 13일
0

[이펙티브자바]

목록 보기
25/72

용어 정리

class Box<T> { }
  • Box<T> : 제네릭 클래스 혹은 제네렉 인터페이스. 제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 한다. 'T의 Box' 또는 'T Box'라고 읽는다
  • T : 타입 변수 또는 타입 매개변수. (T는 타입 문자)
  • Box : 원시 타입(raw type)

로 타입은 타입 매개변수를 사용하지 않은 경우인데, 제네릭이 도입되기 전 코드와 호환되기 위해 있따.
자바 9에서도 여전히 동작하지만 좋은 예가 아니다.

로 타입

// Stamp 인스턴스만 취급할 거다.
private final Collection stamps = ...;

// 실수로 동전 넣는다.
stamps.add(new Coin(...)); // 'unchecked call' 경고를 뱉지만 실행이 된다.

제네릭을 활용하면 타입 선언 자체에서 알 수 있다.

private final Collection<Stamp> stamps = ... ;

타입 안전성이 확보되어서 컴파일 시에 다른 클래스를 넣으려하면 오류가 발생한다.

로 타입을 쓰면 제네릭이 주는 안전성과 표현력을 잃는다. 쓰지말자.

그럼 왜 만들어 놨을까? 호환성 때문이다. 제네릭이 자바 1.5에 나오면서 이전 코드와 호환을 해야하기 때문이다. 그래서 로 타입을 지원하고 제네릭 구현에는 소거(아이템 28)을 사용하기로 했다.

Object와 로 타입 차이점

List와 같은 로 타입은 안되나, List<Object>와 같이 임의 객체를 허용하는 매개변수화 타입은 괜찮다.
List는 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이다.

매개변수로 List를 받는 메서드에 List<String>은 넘길 수 있지만, List<Object>는 넘길 수 없다. 제네릭의 하위 타입 규칙 때문인데 List<String>은 로 타입인 List의 하위 타입이지만, List<Object>의 하위 타입은 아니다 (불공변)

아이템 28. 배열보다는 리스트를 사용하라

그래서 List<Object>같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안정성을 잃게 된다.

와일드 카드

제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표(?)를 사용하자.
예컨데 제네릭 타입인 Set<E>의 비한정적 와일드카드 타입은 Set<?>이다. 이것은 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입이다.

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

와일드카드 타입인 Set<?>와 로 타입인 Set의 차이는, 와일드카드 타입은 안전하고 로 타입은 안전하지 않다는 것이다. 로타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉽다. 반면, Collection<?>에는 (null 외에는) 어떤 원소도 넣을 수 없다. 다른 원소를 넣을려하면 컴파일 할 때 다음의 오류 메시지를 보게된다.

보충 설명

public class test {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        printList(list);

        List<String> listString = List.of("a", "b", "c");
        printList(listString);

        List<?> wildCardList1 = List.of("1", "2", "3");
        printList(wildCardList1);

        List<?> wildCardList2 = new ArrayList<>();
        wildCardList2.add(null);
        wildCardList2.add(1); // 오류 남
        printList(wildCardList2);
    }

    public static void printList(List<?> list) {
        for (Object o : list) {
            System.out.println(o + " ");
        }
    }
}

비한정적 와일드카드를 쓰는 파라미터에는 문제가 없지만, 비한정적 와일드카드로 선언한 Collection에 add를 하게되면 에러 발생.

Object와 비한정적 와일드카드(?)의 차이점.
Object는 형변환, 검사가 매번 필요하고 ?는 컴파일타임에 타입을 지정해준다.

컴파일러는 제 역할을 한 것이다. 즉 컬렉션의 타입 불변식을 훼손하지 못하게 막았다. 구체적으로는 (null외의) 어떤 원소도 Collection<?>에 넣지 못하게 했으며 컬렉션에서 꺼낼 수 있는 객체의 타입도 알 수 없게 했다. 만약 이 제약을 받아들일 수 없다면 제네릭 메서드(아이템 30)나 한정적 와일드 카드(아이템 31)를 쓰면 된다.

Object와 비한정적 와일드카드(?)의 차이점.
https://snoop-study.tistory.com/113

예외

로 타입을 쓰지 말라는 규칙에도 소소한 예외가 있는데, class 리터럴에는 로 타입을 써야 한다.
자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하겠다.

두 번째 예외는 instanceof 연산자와 관련이 있다. 런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다. 다음은 제네릭타입에 instanceof를 사용하는 올바른 예다.

if (o instanceof Set) {    // 로 타입
    Set<?> s = (Set<?>) o; // 와일드카드 타입
}

용어 정리 한번더

0개의 댓글