[Effective Java] 배열보다는 리스트를 사용하라

배열과 제네릭 타입

배열과 제네릭 타입에는 중요한 차이가 두 가지 있다. 첫째는 배열은 공변(共變性)을 가진다. 공변성이란 타입 생성자에게 LSP를 허용한다. 반면 제네릭은 불공변성(不共變性)을 가진다. 다음은 문법상 허용되는 코드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ClassA {
}
public class ClassB extends ClassA{
}
public class ClassC extends ClassA{
}

public static void main(String[] args) {
ClassA[] arr = new ClassB[1];
arr[0] = new ClassC(); //Runtime에 ArrayStoreException을 던진다

List<ClassA> list = new ArrayList<ClassB>(); // 호환되지 않는다. 컴파일타임에 체크된다.
}

두번째 차이로는 배열은 실체화된다. 즉, 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 하지만 제네릭은 타입 정보가 런타임에 소거된다. 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알수조차 없다는 뜻이다.

배열을 제네릭으로 만들 수 없어 귀찮을 때도 있다. 예를 들어 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 보통은 불가능하다. 또한 제네릭 타입과 가변인수 메소드를 함께 쓰면 해석하기 어려운 경고를 받는다. 가변인수 메소드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생하는 것이다. 이는 @SafeVarargs 애너테이션으로 대체할 수 있다.

배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 List를 사용하면 해결된다. 다음 코드는 컴파일되지 않는 코드이다.

1
2
List<ClassA> list = new ArrayList<>();
ClassA[] arr = list.toArray();

오류를 수정한 다음 코드 또한 오류를 낸다.

1
2
List<ClassA> list = new ArrayList<>();
ClassA[] arr = (ClassA[]) list.toArray();

제네릭에서는 원소의 타입 정보가 소거되어 런타임에는 무슨 타입인지 알 수 없음을 꼭 기억하자!

Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/11/Languages/Effective%20JAVA/item28/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.