2023. 12. 5. 22:10ㆍBook/이펙티브 자바
배열과 제네릭 타입에는 중요한 차이가 두 가지 있다.
1. 배열은 공변, 제네릭은 불공변
//String이 Object의 하위 타입이므로 String[]이 Object[]의 하위 타입이 된다
//공변!
Object[] anything = new String[10];
//String이 Object의 하위 타입이므로 List<String>이 List<Object>의 하위 타입이 된다 X
//불공변!
List<Object> names = new ArrayList<String>();
얼핏 보기에는 제네릭에 문제가 있는 것 처럼 보이지만 그렇지 않다.
어느쪽이든 String용 저장소에 Integer 데이터 타입같은 다른 타입을 넣을 수 없다. 배열은 런 타임에 알 수 있지만 리스트를 사용하면 컴파일할 때 바로 알 수 있다.
2. 배열은 실체화 되지만, 제네릭은 실체화 되지 않는다.(소거)
배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다.
반면 제네릭은 컴파일 타임에만 검사하고 런타임에는 타입 정보가 소거되어에 알 수 없다.
무슨 소리인가 할 수 있으니 코드를 살펴보자
List<String> names = new ArrayList<>();
names.add("son");
String name = names.get(0);
System.out.println(name);
==========위 코드는 컴파일러가 바이트 코드로 변환할 때 아래처럼 변한다.===========
List names = new ArrayList();
names.add("son");
Object o = names.get(0);
String name = (String) o;
System.out.println(name);
이상의 주요 차이로 배열과 제네릭은 잘 어울리지 못한다.
배열로 형변환할 대 제네릭 배열 생성 오류나 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신에 List<E>를 사용하면 해결된다. 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 그 대신 타입 안전성과 상호운용성은 좋아진다.
public class Chooser_Array {
private final Object[] choiceList;
public Chooser_Array(Collection choices) {
choiceList = choices.toArray();
}
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList[rnd.nextInt(choiceList.length)];
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);
Chooser_Array chooser = new Chooser_Array(intList);
for (int i = 0; i < 10; i++) {
Number choice = (Number) chooser.choose();
System.out.println(choice);
}
}
}
이 클래스를 사용하려면 choose 메서드를 호출하고 반환된 Object를 원하는 타입으로 형변환해야 한다. 혹시 다른 타입의 원소가 들어있다면 런타임에 형변환 오류가 난다. 이 클래스를 제네릭으로 바꿔보자
public class Chooser_Array<T> {
private final T[] choiceList;
public Chooser_Array(Collection<T> choices) {
choiceList = choices.toArray();
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList[rnd.nextInt(choiceList.length)];
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5, 6);
Chooser_Array<Integer> chooser = new Chooser_Array(intList);
for (int i = 0; i < 10; i++) {
Number choice = chooser.choose();
System.out.println(choice);
}
}
}
이 클래스를 컴파일하면 choice.toArray()메서드가 Object[]을 반환해 컴파일 에러가 나니 아래처럼 형변환을 해주자
public Chooser_Array(Collection<T> choices) {
choiceList = (T[]) choices.toArray();
}
이후엔 경고가 뜰 것이다.
T가 무슨 타입인지 알 수 없으니 컴파일러는 이 형변환이 런타임에 안전한지 보장할 수 없다는 메시지다.
코드를 작성하는 사람이 안전을 확신한다면 주석을 남기고 애너테이션을 달아 경고를 숨겨도 되지만 애초에 경고의 원인을 제거하는 편이 훨씬 좋다
// 코드 28-6 리스트 기반 Chooser - 타입 안전성 확보! (168쪽)
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);
}
}
}
'Book > 이펙티브 자바' 카테고리의 다른 글
Item30 - 이왕이면 제네릭 메서드로 만들라 (1) | 2023.12.06 |
---|---|
Item29 - 이왕이면 제네릭 타입으로 만들라 (1) | 2023.12.06 |
Item27 - 비검사 경고를 제거하라 (2) | 2023.12.05 |
Item26 - 로 타입은 사용하지 말라 (0) | 2023.12.04 |
Item25 - 톱레벨 클래스는 한 파일에 하나만 담으라 (1) | 2023.12.03 |