[Java] Item 28 : 배열보다는 리스트를 사용하라
Effective Java를 읽고 정리한 정리본입니다.
📌 Item 28 : 배열보다는 리스트를 사용하라
🫧 배열과 제네릭 타입
배열과 제네릭 타입은 크게 두 가지의 다른 점이 있다.
- 공변
- 실체화 여부
배열은 공변이자 실체화가 가능하며, 제네릭 타입은 불공변이자 실체화 불가이다.
아래에서 더 자세하게 살펴보자.
✨ 공변 (convariant)
공변이란 함께 변한다는 뜻으로, 하위 타입의 객체를 상위 타입의 참조로 참조할 수 있는 성질을 말한다.
배열은 공변(convariant), 제네릭은 불공변
예를 들어 Sub가 Super의 하위 타입이라면 배열 Sub[]는 Super[]의 하위 타입이 된다.
반면, 제네릭은 불공변으로써 서로 다른 타입 Type1과 Type2가 있을 때 List
✨ 실체화
실체화 타입이란 컴파일 타임에 사용된 타입이 런타임에 소거되지 않는 타입이다.
배열은 실체화 가능, 제네릭은 실체화 불가능
배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 그러나 제네릭은 타입 정보가 런타임에는 소거되기 때문에 원소 타입을 컴파일 타임에만 검사하며 런타임에는 알 수 없다.
이것을 제네릭의 소거 방식이라 부른다.
소거는 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 메커니즘으로, 자바 5가 제네릭으로 순조롭게 전환될 수 있도록 하는 방식이다.
이러한 차이에 의해 배열과 제네릭은 잘 어우러지지 못한다.
가령 new List
타입 안전하지 않기 때문에 자바에서는 제네릭 배열을 허용하고 있지 않는다.
애초부터 제네릭 타입 시스템은 런타임에 ClassCastException이 발생하는 것을 막아주겠다는 취지에서 작성된 것인데, 이를 허용한다면 컴파일러가 자동 생성한 형변환 코드에서 런타임 ClassCastException이 발생할 수 있기 때문이다.
또한, 제네릭 타입과 가변인수 메서드를 함께 쓰지 못한다.
가변 인수 메서드는 “…“와 같이 생겼으며 이를 사용하면 메서드 호출 시 전달되는 인자의 개수를 동적으로 변경할 수 있다.
예시는 다음과 같다.
public void exampleMethod(String... strings) {}
가변인수 메서드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생하는 것이다.
따라 배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 List
🫧 제네릭 적용 예시
✨ 문제 코드
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
choiceArray = choices.toArray();
}
public Ojbect choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
✨ 1차 시도 코드
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = choices.toArray();
}
public Ojbect choose() {
// ...생략
}
}
이렇게 제네릭만 추가한다면 choices.toArray() (Object 배열을 T 배열로 형변환하는 과정) 에서 오류가 날 것이다.
choiceArray(T[]) choices.toArray();
이렇게 Object 배열을 T 배열로 형변환하면 오류가 해결될까?
아쉽게도 아니다.
T가 무슨 타입인지 알 수 없으니 컴파일러는 이 형변환이 런타임에도 안전한지 보장할 수 없다는 경고가 뜬다.
제네릭에서는 원소의 타입 정보가 소거되어 런타임에는 무슨 타입인지 알 수 없기 때문이다.
따라, 배열 대신 리스트를 사용해 이러한 문제를 해결하고자 한다.
✨ 최종 수정 코드
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceList = new ArrayList<>(choice);
}
public Ojbect choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.nextInt(choiceList.size()));
}
}