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

wisdom·2022년 8월 19일
0

Effetctive Java

목록 보기
28/80
post-thumbnail

✔️ 배열 vs 제네릭 타입

1. 배열은 공변(covariant)인 반면, 제네릭은 불공변(invariant)이다.

Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 되지만, List<Sub>은 List<Super>의 하위 타입도 아니고 상위 타입도 아니다.

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

배열은 런타임에도 원소의 타입을 확인한다. 즉, 런타임예외가 발생할 수 있다.
반면, 제네릭은 타입 정보가 런타임에는 소거된다. 원소 타입을 컴파일타임에만 검사하며 런타임에는 알 수조차 없다.

💡 위와 같은 차이로 인해, 배열과 제네릭은 잘 어우러지지 못한다.

예를 들어, 배열은 제네릭 타입(new List<E>[]), 매개변수화 타입(new List<String>[]), 타입 매개변수(new E[])로 사용할 수 없다.

✔️ Chooser 클래스 예제

생성자에서 컬렉션을 받고, 컬렉션 안의 원소 중 하나를 무작위로 선택해 반환하는 choose 메소드를 제공하는 Chooser 클래스를 구현해보자.

📍 제네릭을 쓰지 않은 방법 → 제네릭 적용이 시급하다!

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

이 클래스를 사용하려면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다.
이때 만약 다른 타입의 원소가 들어 있었다면 런타임에 형변환 오류가 발생할 것이다.

따라서 이 클래스는 제네릭 타입으로 만들어야 한다.

📍 제네릭으로 만들기 위한 시도 → 경고

public class Chooser<T> { 
	
    private final T[] choiceArray;
    
    public Chooser(Collection<T> choices) {
    	
        // 1. 컴파일 오류
        // choiceArray = choices.toArray();	
        
        // 2. 경고
        choiceArray = (T[]) choices.toArray();
    }
    
    public Object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}

1번과 같이 작성할 경우, 배열 Object[]이 배열 T[]과 타입이 맞지 않아 컴파일 오류가 발생한다.

이를 해결하기 위해 2번과 같이 작성한 경우, 오류는 발생하지 않지만 런타임에 형변환이 안전한지 보장할 수 없다는 경고 메세지가 나온다. 
이 코드의 경우, 동작하기는 하지만 컴파일러가 안전을 보장하지 못 한다.

📍 리스트 기반 → 타입 안전성 확보

비검사 형변환 경고를 제거하려면 배열 대신 리스트를 쓰면 된다.

다음 코드는 오류나 경고 없이 컴파일된다.

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



📌 핵심 정리

  • 배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다.
  • 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않다.
    제네릭은 그 반대다.
  • 이러한 이유로 배열과 제네릭을 섞어 쓰기란 쉽지 않다.
  • 둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자.
profile
백엔드 개발자

0개의 댓글