배열과 리스트에 대해 설명해주세요
최근 면접 때 받았던 질문이다. 배열과 리스트에 대한 차이를 잠깐 이야기하고,
이펙티브 자바에서는 배열보다 리스트 사용을 권장합니다. 그 이유는 배열은 런타임 시 에러가 발생하지만 리스트는 컴파일 시 에러를 잡아줍니다.
여기에 이어진 질문에 대답을 못했고 이어서 공부해보겠다고 말씀드렸다.
이번 기회에 다시한번 정리해보고자 한다.
배열과 제네릭 타입의 차이
- 배열은 공변(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을 바탕으로 기록하였습니다.
'공부 > 이펙티브자바' 카테고리의 다른 글
54. null이 아닌, 빈 컬렉션이나 배열을 반환하자 (0) | 2022.04.01 |
---|---|
14. Comparable을 구현할지 고려하라 (0) | 2022.03.18 |
12. toString을 항상 재정의하라 (0) | 2022.03.18 |
5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하기 (0) | 2022.03.18 |
64. 객체는 인터페이스를 사용해 참조하라 (0) | 2022.03.17 |
댓글