배열보다는 리스트를 사용하라
배열과 제네릭 타입의 차이점
1. 변할 수 있는가?
- 배열은 함께 변한다. sub가 super의 하위타입이라면, sub[]는 super[]의 하위 타입이 된다.
- 제네릭은 함께 변하지 않는다. 서로 다른 타입 Type1과 Type2가 있을 때, List<Type1>과 List<Type2>는 하위타입도 아니고 상위타입도 아니다.
배열을 쓰면 문제가 생길 수 있다
- 배열에서 에러가 나면 런타임에서 알 수 있어, Exception을 던진다.
- 리스트를 사용하면 컴파일 할때 에러를 바로 알 수 있다.
2. 실체화 되는가?
- 배열은 실체화된다. 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.
- 제네릭은 런타임에 타입 정보가 소거된다. 원소 타입을 컴파일타임에만 검사하며 런타임에는 알 수 없다.
- 소거: 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 메커니즘.
제네릭과 배열이 잘 어우러지지 못하는 이유
- 배열은 제네릭타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다.
- 제네릭 배열을 만들지 못하게 막은 이유: 타입 안전하지 않기 때문
- 만약 제네릭 배열을 만들 수 있다면, 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException 발생
- 런타임에 ClassCastException이 발생하는 일을 막겠다는 제네릭 타입 시스템의 취지에서 어긋나게 된다.
실체화 불가 타입 non-refiable type
E
, List<E>
, List<String>
같은 타입은 실체화 불가 타입이라 한다.
- 실체화되지 않아서 런타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다.
배열은 제네릭으로 만들 수 없다.
- 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 보통은 불가능하다.
- 제네릭 타입과 가변인수 메소드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게 된다.
- 가변인수 메소드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생하는 것이다.
- 배열로 형변환할 때 배열 생성오류 또는 비검사 형변환 경고가 뜰 시, 대부분은 배열
E[]
대신 컬렉션 List<E>
를 사용하면 해결된다.
제네릭이 필요한 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
| 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를 원하는 타입으로 형변환해야한다.
제네릭을 사용하여 수정한 코드
1
2
3
4
5
6
7
8
9
10
11
12
| 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()));
}
}
|