본문 바로가기
Java/Effective Java 3E

[이펙티브자바 3판] ITEM28. 배열보다는 리스트를 사용하라

by 잭피 2020. 10. 14.

이번장의 핵심은...

배열과 제네릭에는 매우 다른 타입 규칙이 적용된다

배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다

배열은 런타임에는 타입 안전하지만 컴파일 타임에는 아니다

제네릭은 반대이다

따라서 둘을 섞어 쓰기는 쉽지 않다.

섞어 쓰다 에러가 나면 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자


배열 vs 제네릭 타입

1. 배열은 공변이다

Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 된다

(즉, 공변이란 함께 변한다는 뜻이다)

반면, 제네릭은 불공변이다

즉, 서로 다른 타입 Type1, Type2가 있을 때, List<Type1>은 List<Type2>의 하위 타입도 아니고 상위 타입도 아니다

제네릭에 문제가 있다고 생각할 수 있지만, 사실문제가 있는 건 배열 쪽이다

 

// 런타임 실패
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException

// 다음 코드는 문법에 맞지 않아 컴파일조차 되지 않음
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다
ol.add("타입이 달라 넣을 수 없다.");

 

2. 배열은 실체화(reify)된다

배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다

Long 배열에 String을 넣으려 하면 ArrayStoreException이 발생

반면, 제네릭은 타입 정보가 런타임에는 소거된다

→ 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수조차 없다는 뜻이다

 

이러한 주요 차이로 인해 배열과 제네릭은 잘 어우러지지 못한다

배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다

제네릭 배열을 만들지 못하게 막은 이유

타입이 안전하지 않기 때문이다

만약 허용한다면 컴파일러가 자동 생성한 형 변환 코드에서 런타임에 ClassCastException이 발생할 수 있다

하지만 이건 제네릭 타입 시스템의 취지에 어긋난다

실체화 불가 타입

E, List<E>, List<String> 같은 타입을 실체화 불가 타입이라 한다

쉽게 말해, 실체화되지 않아서 런 타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다

매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>와 Map<?,?> 같은 비한정적 와일드카드 타입뿐이다

예제) Chooser

// 컬랙션 안의 원소 중 하나를 무작위로 선택해 반환
// 이 클래스를 사용하면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다
// 제네릭을 시급히 적용해야 한다
public class Chooser {
	private final Object[] choiceArray;
	public Chooser(Collection choices) {
		choiceArray = choices.toArray();
	}
	public Object choose() {
		Random rnd = ThreadLocalRandom.current();
		return choiceArray[rnd.nextInt(choiceArray.length)];
	}
}

// 리스트 기반 Chooser - 타입 안전성 확보! (168쪽)
// 코드 양이 조금 늘었지만, 런타임에 ClassCastException을 만날 일은 없으니 가치가 있음
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()));
    }

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

        Chooser<Integer> chooser = new Chooser<>(intList);

        for (int i = 0; i < 10; i++) {
            Number choice = chooser.choose();
            System.out.println(choice);
        }
    }
}

 

이 글은 “이펙티브 자바 3판” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “shk3029@kakao.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.

 

댓글