이펙티브 자바, 쉽게 정리하기 - item 29. 이왕이면 제네릭 타입으로 만들라
제네릭 타입으로 스택 구현하기
Before Generic
static class StackWithArray {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackWithArray() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[size];
elements[size] = null;
return result;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
- 제네릭 타입을 사용하지 않고 단순히
Object
라는 최상위 클래스의 범용성을 이용해 구현했다.
After Generic과 new E[]
의 문제
static class StackWithGeneric<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackWithGeneric() {
// 아래 코드는 에러가 난다.
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
E result = elements[size];
elements[size] = null;
return result;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
- 생성자에서 1차적인 문제에 직면했다.
- 제네릭 코드는
new
키워드로 새로운 클래스를 생성하지 못한다.- 위 코드를 그대로 실행하면,
generic array creation
에러와 마주하게 된다.
- 위 코드를 그대로 실행하면,
제네릭 타입 배열 만들기
제네릭 타입 배열 만들기 1: Object
배열 생성 후 (E[])
로 형변환하기
// Object배열 elements는 push로 들어온 E 타입만을 담는다.
// 타입 안정성을 보장하지만, 이 배열의 런타임 타입은 E[]가 아닌 Object[]다.
@SuppressWarnings("unchecked")
public StackWithGeneric() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
Object
배열을 생성한 뒤에(E[])
와 같이 제네릭 배열로 캐스팅하는 방법이다.- 컴파일러는 이 타입 변환이 안전한지 알 수 없기 때문에 경고를 띄운다.
- 우리는
push()
에서만 유일하게elements
배열에 무언가를 담는다는 것을 알고 있고,push()
는 E 타입만 담으므로 다른 타입이 들어오지 않을 것을 알고 있다.- 그러므로
@SupressWarnings
애노테이션을 이용해 비검사 경고를 지워도 된다.@SupressWarnings
는 항상 가장 작은 범위로 설정하는 것이 좋다.
- 그러므로
제네릭 타입 배열 만들기 2: 새 제네릭 타입 변수에 형변환하여 할당하기
public E pop() {
if(size == 0) {
throw new EmptyStackException();
}
// 새 제네릭 타입 변수에 형변환하여 할당했다.
@SuppressWarnings("unchecked") E result = (E) elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- 새 제네릭 타입 변수에 형변환하여 할당했다.
- 이번에도
SuppressWarnings
위에 주석으로 상세한 이유를 적어두었다.
제네릭 타입 배열을 사용하는 두 가지 방법 장단점 정리
- 첫번째 방식은 형변환을 배열 생성 시 단 한 번만 해주면 된다.
- 두번째 방식은 배열에서 원소를 읽을 때마다 해주어야 한다.
- 첫번째 방식이 더 자주 사용되지만, 배열의 런타임 타입이 컴파일타임 타입과 달라
힙 오염
을 일으킨다.힙 오염
이란 타입 캐스팅 시 컴파일러가 타입 캐스팅 객체를 진짜 캐스팅할 수 있는지 검사하지 않고, 캐스팅했을 때 대입되는 참조변수에 저장할 수 있느냐만 검사하기 때문에 일어나는 현상이다.- 컴파일러는 컴파일 이후에 제네릭 타입 파라미터를 전혀 신경쓰지 않는다.
- 즉,
ArrayList<Integer>
,ArrayList<String>
모두 컴파일 이후에는ArrayList<Object>
가 된다.
- 힙 오염은 잠재적으로
ClassCastException
을 일으킬 수 있다.
핵심 정리
- 클라이언트에서 직접 형변환하기보다 제네릭 타입을 쓰라.
- 제네릭 타입으로 배열을 생성할 때는
Object[]
배열 생성 후(E[])
와 같이 캐스팅하거나, 하나씩E
로 캐스팅하자.
반응형
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.01.24 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item 30. 이왕이면 제네릭 메서드로 만들라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 28. 배열보다는 리스트를 사용하라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 27. 비검사 경고를 제거하라 (0) | 2022.01.09 |
이펙티브 자바, 쉽게 정리하기 - item 26. 로 타입은 사용하지 말라 (0) | 2022.01.09 |