[Effective Java] 5 - item 28 배열보다는 리스트를 사용하라.
item 28 배열보다는 리스트를 사용하라.
배열과 제너릭
배열은 공변이고, 제너릭은 불공변이다.
배열(공변)은 상위 타입(Super
)과 하위 타입(Sub
)의 상속 관계가 유지되고, 제너릭(불공변)은 그렇지 않다.
Object[]objectArray=new Long[1];
objectArray[0]="타입이 달리 넣을 수 없다.";
// 위 코드는 문법상 허용되지만,
// 런타임에 ArrayStoreException을 던진다.
List<Object> ol=new ArrayList<Long>();
ol.add("타입이 달라 넣을 수 없다.");
// 위 코드는 호환되지 않는 타입으로,
// 문법에 맞지 않아 컴파일 단계에서 오류가 발생한다.
배열은 실체화(reify)되고, 제네릭은 소거(erasure)된다.
- 배열은 런타임 시점에 담기로 한 원소의 타입을 인지한다.
- 제네릭은 타입 정보를 컴파일 시점에 검사하고, 런타임에는 소거한다.
이렇듯 배열과 제네릭은 주요한 차이가 있기에, 이 둘을 함께 사용하지 쉽지 않다.
실체화 불가 타입
E, List<E>, List<String>
같은 타입을 실체화 불가 타입(non-reifiable type) 이라 한다.- 실체화되지 않기 때문에 런타임에는 컴파일타임보다 타입 정보를 적게 가진다.
이 때문에, 배열을 제네릭으로 만들 수 없어 귀찮을 때도 있다.
- 제네릭은 자신의 원소 타입을 담은 배열을 반환하는 것이 보통 불가능하다.
- 제네릭과 가변인수 메서드를 함께 사용하면 난해한 경고 메시지가 발생할 것이다. -> 이는
@SafeVarargs
로 대처가 가능하다.
배열로 형변환 할때 오류 혹은 경고가 뜨는 경우, 배열E[]
대신 리스트List<E>
를 사용하라.
코드가 복잡해지고 성능이 나빠질 수 있지만, 타입 안정성과 상호운용성이 좋아진다.
import java.util.concurrent.ThreadLocalRandom;
// 제네릭을 사용하지 않은 예제
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
choiceArray = choices.toArray();
}
public Object choose() {
// 메서드를 호출할 때마다,
// 반환된 Object를 원하는 타입으로 형변환해야 한다.
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
// 제네릭으로 리팩토링하였으나, 비검사 형변환 경고가 발생하는 예제.
// 다음 코드는 동작하지만 컴파일러가 안전을 보장하지 않는다.
public class Chooser<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
choiceArray = (T[]) choices.toArray();
}
}
// 배열 대신 리스트를 사용한 예제
// 타입 안정성이 보장된다.
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));
}
}
Leave a comment