[Effective JAVA 3SE] item 28: 배열보다는 리스트를 사용하라
서론
배열과 제네릭 타입에 두 가지의 중요한 차이가 존재함.
1 . 공변(convariant)여부
>> 배열은 공변임
- Sub(자식)이 Super(부모)의 하위 타입이라면 배열 Sub[ ]는 배열 Super[ ]의 하위 타입이라는 뜻
- 공변은 한자 풀이 그대로 함께 변화한다는 뜻임
>> 제네릭은 불공변임
- 서로 다른 타입 Type 1과 Type 2가 존재할 때, List<Type1>은 List<Type 2>의 하위 타입도 아니고 상위 타입도 아님
서로간의 상위 하위 타입 문제로 접근하자면 제네릭이 더욱 문제있어 보일 수 있지만, 문제가 있는 쪽은 배열임
공변이 왜 문제가되는지 예시 코드를 살펴보겠음.
2. 실체화(Reify) 여부
>> 배열 실체화 가능
- 배열은 런타임에서 자신이 담기로 한 원소의 타입인지 확인함
- 그래서 위에서 보았듯 Long 배열에 String을 넣으면 ArrayStoreException이 발생함
>> 제네릭
- 제네릭은 타입 정보가 런타임에서 소거됨
[제네릭은 런타임에서 타입을 체크해 이를 할당하는 가변성을 가짐]
- 위 말은 컴파일에서 원소 타입을 알 수 없음
- 소거는 제네릭이 지원되기 전 레거시 코드와 제네릭 타입을 함께 사용할 수 이게 해주는 메커니즘으로 ㅈ바 5에서 제네릭으로 순조롭게 전환될 수 있게 해줌 (?????)
- 위 말은 new List<E>[ ], new List<String>[ ]과 같이 작성하면 컴파일 할 때, 제네릭 배열 생성 오류가 발생한다는 것
제네릭으로 배열을 만들지 못하는 이유
정리하자면 타입이 안전하지 않기 때문
- 해당 내용 추가 정리 필요
- 제네릭 배열은 타입이 안전하지 않기에 제네릭 배열을 만들지 못하게 막아져 있음
- 아에 컴파일 자체가 안되기도 함
- E, List<E>, List<String>은 실체화 불가 타입(non-refiable type)
-- 실체화되지 않아 런타임에서 컴파일타임보다 타입 정보를 적게 가지게 됨
-- 소거 매커니즘떄문에 매개변수 타입 가운데 실체화 가능 타입은 List<?>와 Map<?, ?> 과같은 비한정적 와일드카드 타입뿐임 (아이템 26)
- 배열을 제네릭으로 만들 수 없어 귀찮을 수 있음
제네릭 컬렉션에서 자신의 원소 타입을 담은 배열을 반환하는게 보통 불가능함 (이에 대한 해결법은 아이템 33) - 제네릭 타입과 가변인수 메서드를 함께 사용하면 경고 메시지를 받게 됨.
이는 @SafeVarags 로 대처 가능 (아이템 32)
핵심 정리
- 배열과 제네릭은 매우 다른 타입 규칙이 적용됨
- 배열은 공변이고 실체화가 가능한 반면, 제네릭은 불공변에 타입 정보가 소멸됨
- 이로 배열은 런타임에 안전하지 않지만, 컴파일타임에서는 그렇지 않음
- 고로 두 가지를 섞어 사용하는 것은 쉽지 않음
- 둘을 섞어 사용하다 컴파일 오류나 경고 발생시, 가장 먼저 배열을 리스트로 대체하는 방법 적용 할 것