티스토리 뷰

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

아이템 26에서 배열과 리스트의 차이에 공변(convariant)과 불공변(invariant)에 대해 얘기 했다. Object와 String이 부모 자식 관계이지만 List<Object>에 List<String>을 대입할 수 없다. 그러나 배열의 경우 String[]을 Object[]로 타입 캐스팅이 가능하다. 이것이 공변과 불공변의 차이이다.

Object[] objArr = new Long[1];
objArr[0] = "zz"; // -> 여기서 런타임 에러
List<Object> ol = new ArrayList<Long>() // -> 여기서 컴파일 에러
ol.add("zz");

이런 실수를 배열의 경우 런타임에 알지만 리스트는 컴파일 타임에 알 수 있다.
배열과 리스트의 또 다른 차이점은 배열은 실체화(reify)된다. 배열은 런타임에도 자신이 어떤 객체를 담고 있는 지 알지만, 제네릭은 26장에서 얘기 했듯이 런타임에는 그 정보를 가지고 있지 않는다. 이는 제네릭이전의 레거시 코드와의 호환을 위한 조치이다. 이러한 점 떄문에 배열은 제네릭과 잘 어우러지지않는다.
new List<E>[], new List<String>[], new E[] 등등 제네릭으로 배열을 선언 할 수 없게 막혀있다. 이는 런타임에 타입 캐스팅 오류를 알게 되는 일을 막기위한 제네릭의 취지에 반하기 때문에 막았놨다. 옛날에 자바로 코딩테스트 풀다가 안되서 화가 머리 끝까지 났는데 이제 이유를 알게됐다.

List<String> stringLists = new List<String>[1]; // (1)
List<Integer> intList = List.of(42);            // (2)
Object[] objects = stringLists;                 // (3)
objects[0] = intList;                           // (4)
String s = stringLists[0].get(0);               // (5)

이미 충분히 얘기 했듯 (1)에서 컴파일 에러가 난다. (1)이 허용된다고 가정하면 배열은 공변이기 떄문에 (3) 로직이 문제 없이 시행된다. List<String>는 Object의 하위 타입이기 때문이다. 제네릭은 런타임에 실체화되지 않기 때문에 (4) 로직도 성공한다. List에 List를 넣는건 아무 문제가 되지 않는다. (5)는 딱봐도 Integer를 String으로 자동 타입 캐스팅 하려다 Exception이 발생한다. 이를 막기 위해서 (1) 로직을 컴파일 타임에 에러를 발생시킨다.
배열을 제네릭 타입으로 형변활 할 때 오류나 비검사 형변환 오류를 막기 위해선 그냥 List<E>를 쓰면 된다. 성능은 조금 느려지지만 타입 안정성과 상호운용성이 좋아진다.

public class Chooser<T> {
    private final T[] choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = choices.toArray(); //-> Object[]를 반환
    }
}

위의 코드는 컴파일 타임에 에러가 나지만

public class Chooser<T> {
    private final T[] choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = (T[])choices.toArray();
    }
}

위와 같이 타입 캐스팅 해주면 에러가 나지 않는다. 그러나 이녀석은 경고를 내뱉는다. T는 런타임에서 사라지는 정보라 컴파일러가 타입 캐스팅에서 안전함을 보장할 수 없다. 위의 코드는 잘 짰지만, 계속 봐왔던 예제 처럼 배열은 공변이기 때문에 Object[]에 T[]를 넣고 여기에 Integer 같은 친구를 넣고 얘를 꺼내면 런타임에서 타입 캐스팅 에러가 발생한다.

// 코드 28-6 리스트 기반 Chooser - 타입 안전성 확보! (168쪽)
public class Chooser<T> {
    private final List<T> choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

이렇게 리스트를 사용하면 이상한 소스 코드를 컴파일 타임에 알 수 있게 된다. 물론 성능은 조금 느려진다.

 

 

 

GitHub - ssafystudy/EffectiveJava

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

github.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
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
글 보관함