티스토리 뷰
* 이펙티브 자바 2/E를 읽고 공부하기 위해 기록한 게시글입니다.
25. 배열 대신 리스트를 써라
배열은 제네릭 자료형과 중요한 두가지 차이점이 있다.
1. 배열은 공변 자료형(covariant)이고, 제네릭은 불변 자료형(invariant)이다.
2. 배열은 실체화(reifiable)되는 자료형이고, 제네릭은 삭제과정(erasure)을 통해 구현된다.
벌써부터 머리가 아프다. 공변 자료형이라니..
공변 자료형이란 Sub가 Super의 하위 자료형이면 Sub[]도 Super[]의 하위 자료형이라는 것이다.
반면 불변 자료형은 Type1과 Type2가 있을 때 List<Type1>은 List<Type2>의 상위 자료형이나 하위 자료형이 될 수 없다.
이게 왜 중요해? 싶은데 그 이유는 바로 예외를 컴파일 시점에 잡아낼 수 있느냐 없느냐가 갈리기 때문이다. ㄷㄷ
배열을 사용하면 문제가 있다는 사실을 런타임 중에 알게되지만, 리스트를 사용하면 컴파일 시점에 알 수 있다. 당연히 컴파일 시점에 문제를 빨리 찾는 것이 좋다.
// 배열을 사용하는 경우 - 런타임 시에 ArrayStoreException 발생
public static void main(String[] args) {
Object[] arr = new Long[1];
arr[0] = "나는 롱이 아니야";
}
// 리스트를 사용하는 경우 - 컴파일 시에 incompatible types error 발생
public static void main(String[] args) {
List<Object> list = new ArrayList<Long>();
list.add("난 롱이 아니야");
}
Long은 Object의 하위 자료형이기 때문에 Long[]도 Object[]의 하위 자료형이다. 그렇기 때문에 컴파일 시점에는 아무런 문제가 생기지 않는 것.
하지만 리스트는 List<Object>와 List<Long>은 서로 상위/하위 관계가 아니다. 그렇기에 컴파일 시점부터 원천 차단시킨다.
오.. 생각보다 쉽게 이해했다. 그럼 다음 2번으로 넘어가자
제네릭의 동작 과정을 먼저 이해할 필요가 있을 것 같다. 삭제과정을 통해 구현한다는 것이 무슨 말일까??
제네릭은 자료형에 관계된 조건들을 컴파일 시점에만 확인하고 삭제해버리는 식으로 동작한다.
-> 컴파일된 class 파일에는 어떠한 제네릭 타입도 남아있지 않다.
(이러한 방법을 통해 제네릭이 오래된 코드와도 문제없이 연동될 수 있었다)
이러한 차이점 때문에 배열과 제네릭은 섞어서 사용하기가 어렵다.
- 제네릭 자료형이나 형인자 자료형, 형인자 배열을 사용하는 것은 불가능하다.
- 다시말해 제네릭 배열을 만들 수 없다.
(new List<E>[], new List<String>[], new E[] 등... (X))
왜냐하면 형 안전성을 보장할 수 없기 때문이다.
List<String>[] stringList = new List<String>[1]; // 1
List<Integer> intList = Arrays.asList(42); // 2
Object[] objects = stringList; // 3
objects[0] = intList; // 4
String s = stringList.get(0); // 5
실제로는 안되지만 제네릭 배열이 만들어진다고 가정해보자
(1) List<String>을 원소로 갖는 제네릭 배열이 만들어졌다
(2) 하나의 원소를 갖는 List<Integer>를 초기화한다.
(3) List<String> 배열을 Object 배열 변수에 대입한다.
- 배열은 공변 자료형이므로 가능하다.(?)
(4) List<Integer>를 Object 배열에 있는 유일한 원소 자리에 대입한다.
- 제네릭은 형 삭제를 통해 구현되므로 List<Integer>의 런타임 타입은 List이고, List<String>[]의 런타임 타입은 List[]이다
-> 문제없이 들어가진다.
--> List<String>만 담고자 했던 배열에 List<Integer>가 들어가버렸다
(5) stringList 배열의 원소는 List<String>이었기에 원소를 꺼낼 때 컴파일러가 자동으로 String으로 형변환을 해준다. 그런데 정작 들어가있는 원소는 Integer.
-> 결국 ClassCastException 발생. 파국이다
E, List<E>, List<String>과 같은 자료형은 실체화 불가능 자료형으로 알려져있다.
- 즉 프로그램이 실행될 때 해당 자료형을 표현하는 정보의 양이 컴파일 시점에 필요한 정보의 양보다 적은 자료형이 실체화 불가능 자료형이다. (런타임에 해당 자료형의 모든 정보를 이용할 수 없는 자료형)
- 실체화 가능한 형인자 자료형은 비한정적 와일드카드 자료형("?") 뿐인데, 이건 쓸 일이 없으니 사실상 없다고 취급.
(결론)
제네릭과 배열이 따르는 제네릭 규칙은 서로 많이 다르다.
> 배열은 공변 자료형이자 실체화 가능 자료형이다.
> 제네릭은 불변 자료형이며, 런타임에 형인자의 정보가 삭제된다
따라서 배열은 컴파일 시간에 형 안전성을 보장하지 못하며, 제네릭은 그와 반대이다.
-> 그렇기에 배열과 제네릭은 혼용할 수 없다. (제네릭 배열은 만들 수 없다)
'JAVA' 카테고리의 다른 글
enum에서 ordinal 메서드를 사용하지 마라 (0) | 2022.07.27 |
---|---|
상수 대신 Enum을 사용하라 (2) | 2022.07.27 |
unchecked warning은 제거하라 (0) | 2022.07.26 |
raw type은 사용하지 마라 (0) | 2022.07.25 |
자바의 내부 클래스와 중첩 클래스에 대해.. (0) | 2022.07.25 |