Skip to content

Latest commit

 

History

History
49 lines (31 loc) · 3.28 KB

배열보다는_리스트를_사용하라.md

File metadata and controls

49 lines (31 loc) · 3.28 KB

배열보다는 리스트를 사용하라

배열과 제네릭 타입에는 중요한 차이가 두 가지 있다.

1. 배열은 공변이다. 반면, 제네릭은 불공변이다.

Sub가 Super의 하위 타입이라면 Sub[]Super[]의 하위 타입이 된다. 반면, 서로 다른 타입 Type1과 Type2가 있을 때, List<Type1>List<Type2>의 하위 타입도, 상위 타입도 아니다.

  • Super[] a = new Sub[1]; (O), List<Super> b = new ArrayList<Sub>(); (X)

이것만 보면 제네릭에 문제가 있다고 생각할 수도 있지만 사실 문제가 있는 건 배열 쪽이다. 배열에서는 실수를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일할 때 바로 알 수 있다.

// 런타임에 실패한다.
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다.

// 컴파일되지 않는다!    
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다.
ol.add("타입이 달라 넣을 수 없다.");

2. 배열은 실체화(reify, 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인)된다. 반면, 제네릭은 타입 정보가 런타임에는 소거된다.


이상의 주요 차이로 인해 배열과 제네릭은 잘 어우러지지 못한다.

  • 예컨대 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. (new List<E>[], new List<String>[], new E[])

제네릭 배열을 만들지 못하기 막은 이유는 타입 안전하지 않기 때문이다. 이를 허용한다면 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다.


E, List<E>, List<String> 같은 타입을 실체화 불가 타입이라 한다.

  • 실체화되지 않아서 런타임에는 컴파일타임보다 타입 정보를 적게 가지는 타입이다.

소거 메커니즘 때문에 매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>Map<?,?> 같은 비한정적 와일드카드 타입뿐이다.
배열을 비한정적 와일드카드 타입으로 만들 수는 있지만, 유용하게 쓰일 일은 거의 없다.

참고: 타입 소거를 한다고 해도 잃을 정보가 없기 때문에 실체화 타입이라 볼 수 있는 것이다. 컴파일 시점에 Object로 변환된다. (출처)


제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메시지를 받게 된다.

  • 가변인수 메서드를 호출하면 가변인수 매개변수를 담을 배열이 만들어지는데, 그 배열의 원소가 실체화 불가 타입이라면 경고 발생

배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 컬렉션인 List<E>를 사용하면 해결된다. 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 런타임에 ClassCastException을 만날 일은 없으니 그만한 가치가 있다.