💥 개요
JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다. 그래도 배워두면 값어치는 충분히 한다.
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 result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
위 코드는 제네릭이 절실한 강력 후보다. 이 클래스는 원래 제네릭 타입이어야 마땅하다.
이 클래스를 제네릭으로 만들어도 현재 버전을 사용하는 클라이언트에는 아무런 해가 없다. 오히려 지금 상태는 스택에서 꺼낸 객체를 형변환 하는 과정에서 ClassCastException이 런타임 과정에서 발생할 가능성이 있다.
🛠️ 제네릭 클래스로 변경
첫 단계는 클래스 선언에 타입 매개 변수를 추가하는 일이다. 이때 타입으로는 일반적으로 E를 사용한다.
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 result;
}
...
}
이 단계에서 문제가 하나 이상 발생하는데 여기서는 Stack을 선언하는 과정에서 문제가 발생한다.
이전에 말했던 것 처럼, E와 같은 실체화 불가 타입은 배열을 만들 수 없다.
해결책 1 - Object로 생성 후 E[]로 형변환
이 방법은 경고가 발생하지만, 컴파일러로 검사할 수 없기 때문에 우리가 직접 안전한지 확인해야한다. 안전하다면 @SuppressWarnings 어노테이션으로 최소한의 범위를 잡아 경고를 숨긴다.
해결책 2 - elements를 Object[]로 변경 후 pop하는 경우 형변환
역시 E는 실체화 불가 타입이라 컴파일러는 런타임에 일어나는 형변환이 안전한지 증명할 수 없다. 이번에도 스스로 확인해서 경고를 숨길 수 있다.
첫번째 방법은 현업에서 선호하며 자주 사용한다. 하지만 E가 Object가 아닌 한 배열의 런타임 타입이 컴파일타임 타입과 달라 힙오염을 일으킨다. 두번째 방법은 힙 오염을 일으키지 않아 마음에 걸린다면 두번째를 사용하기도 한다.
🔍 제네릭의 특징
- 배열보다 리스트를 사용하라와 모순돼 보이지만, 항상 리스트가 좋은것은 아니다. (성능, 못하는 경우도 있음)
- 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다. 단, 기본타입은 사용할 수 없다.
- 타입 매개변수에 제약을 두는 제네릭 타입도 있다. (java.util.concurrent.DelayQueue, 한정적 타입 매개변수)
클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.
그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라.
그렇게 하려면 제네릭 타입으로 만들어야 할 경우가 많다.
기존 타입 중 제네릭이었어야 하는 게 있다면 제네릭 타입으로 변경하자.
기존 클라이언트에는 아무 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다.
'Study > 이펙티브 자바' 카테고리의 다른 글
[Effective Java 3E] 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.12.04 |
---|---|
[Effective Java 3E] 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2023.12.02 |
[Effective Java 3E] 배열보다는 리스트를 사용하라 (0) | 2023.11.10 |
[Effective Java 3E] 비검사 경고를 제거하라 (1) | 2023.11.09 |