티스토리 뷰

26. 로 타입은 사용하지 말라

갑자기 로 타입이라고 하길래 이 친구가 무슨 녀석인지 부터 알 필요가 있다.
쉽게 말해 제네릭 타입 List<E>의 로(raw) 타입은 List가 된다.
클래스의 매개 변수를 제네릭을 통해 특정 클래스로 제안하지 않으면 발생할 문제들이 눈에 선하다
제네릭이 생기까지는 10년의 세월이 흘러 기존의 코드와의 호환성을 위해
raw 타입을 남겨뒀다고한다.

// Stamp 클래스만 취급합니당 ^^7
private final Collection stamps = ...;
stamps.add(new Coin());

위와 같은 Collection을 raw 타입으로 선언하고 주석으로 열심히 Stamp 객체만 달라고 해도 무시하고 Coin을 넣는다.
컴파일러는 경고를 unchecked call 경고를 뱉지만 이것도 무시하기로 한다.

for (Iterator i = stamps.iterator(); i.hasNext();){
    Stamp stamp = (Stamp) i.next();
    ...
}

위와 같은 코드를 작성한다고 했을 때 우리는 런타임에서야 ClassCastException을 확인할 수 있다. (물론 Stamp는 Coin의 상위 타입이 아니다)
이를 컴파일 타임에 알 수 있었다면 좋았을 것이다.

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

위와 같이 Collection에서 취급할 클래스를 제네릭을 통해 명시 해주면 stamps에 Coin을 넣으려고 하면 이를 컴파일 타임에 알 수 있게 된다.
List와 같은 raw 타입은 안되지만 List<Object>와 같은 임의 객체 허용 타입은 괜찮다.
이 둘의 차이는 무엇일까? 바로 List를 parameter로 받는 상황에서 차이가 생긴다.

// 코드 26-4 런타임에 실패한다. - unsafeAdd 메서드가 로 타입(List)을 사용 (156-157쪽)
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);
    }
}

위의 코드와 같이 raw 타입의 parameter를 받으면 Integer가 들어간 List에서 Integer를 String으로 type casting할 때 ClassCastException이 발생한다.

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<Object> list, Object o) {
        list.add(o);
    }
}

코드를 위와 같이 Object 제네릭 타입으로 parameter를 받으면 List<String>을 넣는 순간에 컴파일 에러가 발생한다.
이 이유가 또 재밌는데 모든 제네릭 List 타입들은 raw 타입인 List의 하위 타입이 된다. 따라서 List<String> -> List로 타입 캐스팅은 가능하지만 List<String> -> List<Object>는 불가능 하다. 이는 List<String>과 List<Object>가 각각 List를 상속 받은 형제 관계 클래스라고 생각하면 쉽다. 형제 관계에서는 타입 캐스팅이 불가능하다. 이와 같은 관계를 불공변이라고한다. 신기하게 배열은 공변이라 Integer[] -> Object[]로 타입 캐스팅이 가능하다.

그렇다면 모든 것을 다 받는 클래스를 설계하고 싶을 땐 어떻게 하면 좋을까? List<?>와 같이 와일드카드 타입을 사용하면된다.
물론 그냥 ?만 쓰면 null을 제외한 어떤 클래스도 추가할 수 없다.
와일드 카드는 any가 아닌 unknown 타입으로 취급된다. 따라서 어떤 클래스를 받을 지를 고르기 위해선 와일드 카드의 상한과 하한을 정해줘야한다. 이는 아이템 31 한정적 와일드 카드에서 배울 수 있다.

raw 타입은 사용해선 안된다고 했지만 예외가 있다. class 리터럴에는 raw 타입을 써야한다.

배열과 premetive 타입을 제외하고 class 리터럴은 parametric 타입을 허용하지 않는다. ex) String[].class, int.class 됨 // List<String>.class 안 됨
두번째로는 instanceof 연산자에서는 raw 타입을 써야한다. 제네릭이나 와일드카드 같은 경우는 컴파일 타임에서 에러를 잡기 위함이라 런타임에서는 제네릭 타입의 정보가 지워진다.

if (o instanceof Set){ // 그냥 raw를 쓰자
    ...
}

따라서 이러한 경우엔 제네릭, 와일드카드는 raw 타입과 똑같이 동작하므로 코드가 간단한 raw 타입을 쓰자.

 

 

 

GitHub - ssafystudy/EffectiveJava

Contribute to ssafystudy/EffectiveJava development by creating an account on GitHub.

github.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함