리스트가 사용하기 더 편한 것을 제외하고서라도 배열보다는 리스트의 사용을 권장하는 이유가 있습니다.
배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거됩니다. 그 결과 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않습니다. 제네릭은 반대로 런타임에는 타입 안전하지 않지만 컴파일타임에는 안전합니다.
배열은 공변(covariant)
Sub가 Super의 하위 타입이라면 Sub[]는 Super[]의 하위 타입입니다. 이를 공변이라고 합니다. 반면 제네릭은 불공변(invariant, 무변성)입니다.
public class Application {
public static void main(String[] args) {
Object[] objArr = new Integer[1];
List<Object> ol = new ArrayList<Integer>(); //컴파일 에러
}
}
위 코드는 java: incompatible types: java.util.ArrayList<java.lang.Integer> cannot be converted to java.util.List<java.lang.Object>
라는 컴파일 에러를 발생시킵니다.
근데 배열쪽에도 문제가 있습니다. objArr에 String 타입인 "100"을 넣는다면 컴파일은 되지만 런타임시 ArrayStoreException
이 발생합니다.
결국 둘 다 Intger용 저장소에 String을 넣을 수는 없지만 배열은 런타임에야 알게 되지만, 리스트를 사용하면 컴파일 타임때 바로 알 수 있습니다.
배열은 실체화(reify)
배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인합니다. 그래서 위의 예에서 ArrayStoreException
이 발생한 것입니다.
반면, 제네릭은 런타임에는 소거되어 담기로 한 원소 타입을 알 수 없습니다.(기존 코드와의 호환성때문에 이렇게 설계)
배열과 제네릭
위의 두 차이(공변, 실체화)로 인해 배열과 제네릭은 잘 어우러지지 못합니다.
배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없습니다. 즉, new List<e>[], new List<String>[], new E[]와 같이 작성하면 컴파일 타임에 제네릭 배열 생성 오류를 일으킵니다. 그래서 E, List<E>, List<String>같은 타입을 실체화 불가 타입(non-refiable type)이라고 합니다. 실체화되지 않기 때문에 런타임에는 컴파일 타임보다 타입 정보를 더 적게 가지는 타입입니다.
이러한 소거 메커니즘 때문에 매개변수화 타입 가운데 실체화 될 수 있는 타입은 List<?>, Map<?, ?> 같은 비한정적 와일드카드 타입뿐입니다. 하지만 배열을 비한정적 와일드카드 타입으로 만들 수는 있지만 유용하게 쓰일 일은 거의 없습니다.
'스터디 > 이펙티브 자바' 카테고리의 다른 글
[Effective Java] Item30. 이왕이면 제네릭 메서드로 (0) | 2022.06.18 |
---|---|
[Effective Java] Item29. 이왕이면 제네릭 타입으로 (0) | 2022.06.12 |
[Effective Java] Item27. 비검사 경고를 제거 (0) | 2022.06.11 |
[Effective Java] Item26. 로(raw) 타입은 사용하지 말라 (0) | 2022.06.11 |
[Effective Java] Item25. 톱레벨 클래스는 한 파일에 하나만 (0) | 2022.06.11 |