본문 바로가기
공부/이펙티브자바

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

by JERO__ 2022. 10. 31.

배열과 리스트에 대해 설명해주세요

최근 면접 때 받았던 질문이다. 배열과 리스트에 대한 차이를 잠깐 이야기하고,

이펙티브 자바에서는 배열보다 리스트 사용을 권장합니다. 그 이유는 배열은 런타임 시 에러가 발생하지만 리스트는 컴파일 시 에러를 잡아줍니다.

여기에 이어진 질문에 대답을 못했고 이어서 공부해보겠다고 말씀드렸다.

이번 기회에 다시한번 정리해보고자 한다.

배열과 제네릭 타입의 차이

  • 배열은 공변(covaiant)이다.

공변은 함께변한다라는 뜻이다.

예를 들어 Sub가 Super의 하위 타입이라면, Sub[]는 배열 Super[]의 하위 타입이 된다. 이를 공변이라 한다. 즉, 함께 변한다.

  • 제네릭은 불공변(invariant)이다.

불공변은 함께 변하지 않는다라는 뜻이다.

List<Sub>는 List<Super>의 하위타입이 아니고 상위타입도 아니다.

즉, Long 타입용 저장소에 String 타입을 넣을 수 없다.

 

예제코드로 살펴보자.

Object[] objectArray = new Long[1];
objectArray[0] = "Kimtaeng";  // ArrayStoreException 발생, 런타임 에러 !

// 컴파일 에러
List<Object> objectList = new ArrayList<>();
objectList.add(1L);

배열은 런타임에 에러를 잡아냈고, 리스트는 컴파일에 에러를 잡아냈다.

다시 한번 기회가 온다면 이렇게 말씀드릴 것 같다.

 

자바에서 배열보다는 리스트를 사용하는 이유?

  • 만약 Long 타입의 배열을 생성하고 0번째에 String 값을 넣으면 런타임 시점에 에러가 발생한다.
  • 만약 Long 타입의 리스트를 생성하고 String 값을 추가하면 컴파일 에러가 발생한다.
  • 리스트는 제네릭 타입을 받으며, 제네릭 타입은 ClassCastException을 막아주도록 설계되었기 때문이다.
  • 배열을 제네릭으로 만들지 못하는 이유는 타입이 안전하지 않기 때문이다. 만약 허용한다면 ClassCastException이 발생할 수 있고 이는 제네릭 타입 시스템 취지와 어긋난다.

 

제네릭에 대한 이해

그동안 나는 제네릭 정의를 다음 정도로만 알았다.

: 제네릭을 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것이다.

 

이번 질문을 통해 다음 사실도 알게되었다.

: 제네릭의 본질은, 런타임에 ClassCastException이 발생하는 것을 막아주어 타입 안전성을 높이는데 있다.

 

배열로 형변환시 오류가 발생한다면 어떻게 처리할까?

배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[]  대신에 컬렉션인 List<E>를 사용하면 해결된다.

// 컬랙션 안의 원소 중 하나를 무작위로 선택해 반환
// 이 클래스를 사용하면 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을 바탕으로 기록하였습니다.

댓글