본문은 Effective Java를 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
선결론
- 매우 다른 타입 규칙이 배열과 제네릭에 적용된 결과, 배열은 런타임에 타입 안전한 반면 컴파일에 그렇지 않고, 제네릭은 그 반대다
- 따라서 둘을 섞어 쓰는 것을 지양하고, 배열 대신 리스트를 사용하라
배열과 제네릭 타입의 차이
1. 배열은 공변(covariant)인 반면 제네릭은 불공변(invariant)이다
- 공변이란?
- Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 되는 것
- 즉, 함께 변한다는 뜻
배열(공변)
class UsingArray {
public static void main(String[] args) {
Object[] objectArray = new Long[1]; // ArrayStoreException, 런타임 에러
objectArray[0] = "String";
}
}
- Long[]은 Object[]의 하위타입이다
- 런타임에 실패한다
제네릭(불공변)
class UsingList {
public static void main(String[] args) {
List<Object> objectList = new ArrayList<Long>(); // 호환되지 않는 타입, 컴파일 에러 발생
objectList[0] = "String";
}
}
- ArrayList<Long>은 List<Object>의 하위타입이 아니다
- 컴파일에 실패한다
2. 배열은 실체화(reify)되는 반면 제네릭은 실체화되지 않는다
- 배열은 런타임에 자신이 담기로한 원소의 타입을 인지하고 확인한다
- 예컨대 위 코드에서 Long 배열에 String을 넣으려 하면 ArrayStoreException이 발생한다
- 배열이 실체화된다는 것은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인하는 것을 의미한다
- 반면, 제네릭은 런타임에 타입 정보가 소거(erasure)된다
- 원소 타입을 컴파일타임에만 검사하고 런타임에는 알 수 없다
- 이에 따라 E, List<E>, List<String>과 같은 타입을 실체화 불가 타입(non-reifiable type)이라고 한다
- 즉, 실체화 되지 않아서 (소거에 의해) 런타임에는 컴파일타임보다 타입 정보를 적게 가지는 타입이다
- 매개변수화 타입 가운데 실체화될 수 있는 타입은 List<?>와 Map<?,?> 같은 비한정적 와일드카드 타입뿐이다
- 사실 이 부분이 잘 이해가 되지 않았다.. 뒷부분까지 읽어보고 생각한 것은 다음과 같다(다른 의견이 있다면 남겨주시길..)
- E, List<E>, List<String>와 같은 타입은 소거를 통해 Object, List<Object>의 형태로 바뀐다.
- E나 String과 같은 타입 매개변수는 Object의 서브 클래스로서 더 많은 정보를 가지지만, 런타임 시점에 Object로 바뀐다
- 이러한 맥락에서 런타임에 컴파일타임보다 타임 정보를 적게 가진다고 할 수 있다
- 사실 이 부분이 잘 이해가 되지 않았다.. 뒷부분까지 읽어보고 생각한 것은 다음과 같다(다른 의견이 있다면 남겨주시길..)
- 소거란?
- 제네릭이 지원되기 전의 레거시 코드와 제네릭 타입을 함께 사용할 수 있게 해주는 메커니즘
- 런타임에 unbounded type parameter를 Object로 바꾼다
- <T>, <?> → Object
- 런타임에 bounded type parameter를 bounds로 바꾼다
- <T extends Comparable<T>> → Comparable
- 필요한 경우 타입 안정성을 보장하기 위해 타입 캐스팅을 추가한다
- 확장된 제네릭 타입에서 다형성을 보장하기 위해 브릿지 메서드를 생성한다
제네릭 클래스 Node
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
런타임 시점의 Node
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
다음은 브릿지 메서드를 설명하기 위한 예시이다
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
- Node 클래스와 이를 상속하는 MyNode 클래스가 있다
위 코드에서 소거가 일어나면 아래와 같이 된다
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
- 이때, Node.setData의 시그니처가 달라지는 문제가 발생하게 된다
- Node.setData(T) → Node.setData(Object)
- 그 결과로 MyNode.setData는 Node.setData를 오버라이딩할 수 없게 된다
이 문제를 해결하고 제네릭 타입의 다형성을 보장하기 위해 자바 컴파일러는 다음과 같은 브릿지 메서드를 생성한다
class MyNode extends Node {
// Bridge method generated by the compiler
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
- 브릿지 메서드 MyNode.setData(Object)는 MyNode.SetData(Integer) 메서드를 대신한다
- 그 결과 Node.setData("Hello")와 같은 구문은 ClassCastException을 발생시키게 된다
제네릭 배열을 만들 수 없다
- 위의 차이로 배열과 제네릭은 잘 어울리지 못한다
- 예컨대 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용 불가능하다
- 즉, new List<E>[], new List<String>[], new E[]와 같이 코드를 작성하면 제네릭 배열 생성 오류를 일으킨다
- 제네릭과 배열을 혼합하여 사용하게 되면 타입 안전하지 않다
- 제네릭을 사용한 리스트와 배열을 혼합하여 사용하면 공변으로 인해 ClassCastException이 발생할 수 있다
- 이러한 이유에서 제네릭 배열이 생성되지 않도록 막은 것이다
- 따라서 제네릭을 사용할 땐 리스트를 사용하도록 한다
참고
'책 > Effective Java' 카테고리의 다른 글
[이펙티브 자바] 아이템 32: 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.05.24 |
---|---|
[이펙티브 자바] 아이템 30: 이왕이면 제네릭 메서드로 만들라 (0) | 2022.05.20 |
[이펙티브 자바] 아이템 27: 비검사 경고를 제거하자 (0) | 2022.05.13 |
[이펙티브 자바] 아이템 26: 로 타입은 사용하지 마라 (0) | 2022.05.12 |
[이펙티브 자바] 아이템 25: 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2022.05.11 |