[Effective Java] 5 - item 29 이왕이면 제너릭 타입으로 만들라.
item 29 이왕이면 제너릭 타입으로 만들라.
다음은 제너릭을 사용하지 않은 Stack
클래스이다.
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 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;
}
... // 타 메서드는 동일하다.
}
이를 해결하기 위해서는,
Object
배열로 생성 후 제네릭으로 타입캐스팅 하거나elements
필드를 제네릭 배열E[]
에서Object[]
로 변경한다.
Object
배열을 제너릭 배열으로 타입캐스팅
비검사 형변환으로, Object 배열 생성 후 제너릭으로 형변환하는 방법이다.
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
// ^ Unchecked cast 발생 :
// 컴파일러에서 타입 안정성이 보장되지 않는다는 경고가 발생한다.
}
elements
는private
필드에 저장되며, 클라이언트로 반환되거나 다른 메서드로 전달되는 일이 없다.push
메서드를 통해 배열에 저장되는 원소의 타입은 항상 E이다.
따라서 이 비검사 형변환이 type-safe함을 확인할 수 있으므로, @SuppressWarnings
어노테이션으로 경고를 숨긴다.
- 위와 같은 방식에서는 형변환을 배열 생성시 최초 1회만 해주면 된다.
- 하지만 런타임 타입과 컴파일타임 타입과 달라 *힙 오염이 발생할 수 있다.
*힙 오염 : JVM 메모리 공간인 Heap 영역에서, 매개변수화 타입 변수가 다른 타입의 객체를 참조해 발생한다.
필드 타입을 제네릭 배열E[]
에서 객체 배열인 Object[]
로 변경
변경하게 되면, pop
메서드의 element
원소 접근 과정에서 오류가 발생하게 된다.
E result = elements[--size];
// ^ incompatible type
// 제네릭 타입에 Object 타입이 할당되려 한다.
문제가 되는 부분은은 첫번째 방법에서의 해결법처럼, (여기에서는 메서드 내에서의) 타입캐스트와 @SuppressWarnings
로 해결할 수 있다.
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 E pop() {
if (size == 0)
throw new EmptyStackException();
// push에서 E 타입만 허용하므로 이 형변환은 안전하다.
@SuppressWarnings("unchecked") E result = (E) elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
// etc ...
}
두번째 방식에서는 코드가 길어지고 배열에서 원소를 읽을 때마다 형변환을 해줘야 하는 단점이 있지만, 힙 오염은 일어나지 않는다.
한정적 타입 매개변수 (Bounded Type Parameter)
- 타입 매개변수에 제약을 두는 제네릭 타입이다.
Upper bounded type
class UpperBoundedClass<E extends Example>
와 같이, 제네릭 타입에extends
를 사용한다.- 특정 클래스(
Example
)의 자기 자신 및 하위 타입만 타입 변수로 받을 수 있다. - 클래스(
UpperBoundedClass
) 내에서 타입 매개변수(UpperBoundedClass
)의 메서드를 호출할 수 있다.
- 특정 클래스(
Lower bounded type
class LowerBoundedClass<E super Example>
와 같이, 제네릭 타입에super
를 사용한다.- 특정 클래스(
Example
)의 자기 자신 및 상위 타입만 타입 변수로 받을 수 있다.
- 특정 클래스(
Leave a comment