-
Item 28. 배열보다는 리스트를 사용하라Book/Effective Java 3E 2022. 11. 12. 09:10반응형
배열과 제네릭 타입의 차이
- 배열은 공변(convariant)이다. Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 된다(공변, 즉 함께 변한다는 뜻이다). 반면, 제네릭은 불공변(invariant)이다. 즉, List<Type1>은 List<Type2>의 하위 타입도 아니고 상위 타입도 아니다.
- 배열은 실체화(reify)된다. 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다(아래 코드 28-1 런타임 실패). 반면, 제네릭은 타입 정보가 런타임에는 소거(erasure)된다. 원소 타입을 컴파일타임에만 검사한다. 소거는 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 메커니즘이다.
// 코드 28-1 런타임에 실패한다. Object[] objectArray = new Long[1]; objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다. // 코드 28-2 컴파일되지 않는다. List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다. ol.add("타입이 달라 넣을 수 없다.");
어느 쪽이든 Long용 저장소에 String을 넣을 수는 없습니다. 다만 배열에서는 그 실수를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일할 때 바로 알 수 있습니다.
배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우, 대부분은 배열인 E[] 대신 컬렉션인 List<E>를 사용하면 해결됩니다. 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 그 대신 타입 안정성과 상호운용성은 좋아집니다.
생성자에서 컬렉션을 받는 Chooser 클래스를 예로 살펴봅시다. 이 클래스는 컬렉션 안의 원소 중 하나를 무작위로 선택해 반환하는 choose 메서드를 제공하며, 생성자에 어떤 컬렉션을 넘기느냐에 따라 주사위판, 매직 8볼, 몬테카를로(Monte Carlo) 시뮬레이션용 데이터 소스 등으로 사용할 수 있습니다.
// 코드 28-3 Chooser - 제네릭을 시급히 적용해야 한다! 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)]; } }
이 클래스를 사용하려면 choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 합니다. 혹시나 타입이 다른 원소가 들어 있었다면 런타임에 형변환 오류가 날 것입니다. 이 클래스를 제네릭으로 만들면 다음과 같습니다.
// 코드 28-4 Chooser를 제네릭으로 변환 public class Chooser<T> { private final T[] choiceArray; public Chooser(Collection<T> choices) { // choiceArray = choices.toArray(); // 컴파일되지 않는다. choiceArray = (T[]) choices.toArray(); // warning: [unchecked] unchecked cast } // choose 메서드는 그대로다. }
컴파일 실패를 피해 Object 배열을 T배열로 형변환하면 warning이 발생합니다. T가 무슨 타입인지 알 수 없으니 컴파일러는 이 형변환이 런타임에도 안전한지 보장할 수 없다는 메시지입니다.
비검사 형변환 경고를 제거하려면 배열 대신 리스트를 쓰면 됩니다. 다음 Chooser는 오류나 경고 없이 컴파일됩니다.
package effectivejava.chapter5.item28; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; // 코드 28-5 리스트 기반 Chooser - 타입 안전성 확보! 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); } } }
배열과 제네릭에는 매우 다른 타입 규칙이 적용된다.
배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다.
그 결과 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않다.
제네릭은 반대다.
둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대처하는 방법을 적용해보자.
[참고 정보]
이펙티브 자바 Effective Java 3/E 도서 [조슈아 블로크 저]
반응형'Book > Effective Java 3E' 카테고리의 다른 글
Item 30. 이왕이면 제네릭 메서드로 만들라 (0) 2022.11.12 Item 29. 이왕이면 제네릭 타입으로 만들라 (0) 2022.11.12 Item 27. 비검사 경고를 제거하라 (0) 2022.11.08 Item 26. 로 타입은 사용하지 말라 (0) 2022.11.08 Item 25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) 2022.11.06