이펙티브 자바 - item 29. 이왕이면 제네릭 타입으로 만들라
on JAVA
이펙티브 자바 - item 29. 이왕이면 제네릭 타입으로 만들라
단순 스택 코드를 제너릭으로 변경해보자.
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
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 elements[--size];
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
위의 예시는 스택에서 꺼낸 객체를 형변환할 때 런타임 오류가 발생할 위험이 있다. 위의 코드를 제너릭 클래스로 변경해보자.
클래스 선언에 타입 매개변수를 추가한다.
public class Stack<E> {
private E[] elements; // 타입 매개변수 추가.
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
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 elements[--size];
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
이렇게 타입 매개변수를 추가하면 오류가 발생한다. E와 같은 실체화 불가 타입으로 배열을 만들 수 없어 생기는 오류이다.
이는 배열을 사용하는 코드를 제네릭으로 만들려 할 때 마다 마주쳐야하는 에러이다.
이를 해결하기 위해서는 두 가지 해결책이 존재한다.
- 제너릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; // Object 배열을 선언한 다음 제너릭 배열로 형변환
}
...
}
위의 코드와 같이 Object 배열을 생성한 다음 제너릭 배열로 형변환하면 컴파일러가 오류 대신 경고를 내보내는 것을 확인할 수 있다. 하지만 이는 일반적으로 타입 안전하지 않다.
따라서 이 비검사 형변환이 프로그램의 타입 안정성을 해치지 않음을 개발자가 직접 확인하고 @SuppressWarnings
애너테이션으로 해당 경고를 숨겨야 한다.
- elements 필드의 타입을 E[]에서 Object[]로 수정한다.
public class Stack<E> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
// 경고가 뜨는 부분
@SuppressWarnings("unchecked") E result = (E) elements[--size];
elements[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
이번에는 pop에서 경고가 뜨게 되는데 이 부분을 @SuppressWarnings("unchecked")
처리하여 경고를 숨겨주자
첫번 째 방법이 형변환을 배열 생성 시에 한번만 해주고 가독이 더 좋아 개발자들이 더 선호하며 자주 자주 사용하지만, 배열의 런타임 타입이 컴파일 타입과 달라 힙 오염을 일으킨다.
한정적 타입 매개변수를 사용한 제네릭
class DelayQueue<E extencds Delayed> implements BlockingQueue<E>
타입 매개변수에 제약을 두는 제네릭 타입도 있다. 위의 코드는 Delayed의 하위 타입만 매개변수로 받는다는 의미이다. 클라이언트는 DelayQueue에서 형변환 없이 곧바로 Delayed의 메서드를 호출 할 수 있다.
모든 타입은 자기 자신의 하위 타입이므로 DelayQueue
정리
클라이언트가 직접 형변환을 해야하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.
그러니 새로운 타입을 설계할 때는 형변환 없이도 사용 할 수 있게 하라.
가장 좋은 방법은 제네릭 타입을 사용하는 방법이다.