Effective Java - Item 29
Updated:
Effective Java
Item 29. 이왕이면 제네릭 타입으로 만들라
제네릭 클래스로 만들기
- 타입 매개변수를 만들자.
- 타입의 이름으로 보통 E를 사용한다.
- Object를 적절한 타입 매개변수로 변경한다.
public class Stack<E> {
private E[] elements; // Object를 E로, 두번째 방법은 Object[] elements로
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = new E[DEFAULT_INITIAL_CAPACITY]; // 첫번째 방법, (E[]) new Object [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]; // 두번째 방법, 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);
}
}
}
컴파일 오류 발생!!!
E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.
따라서, 두가지 방법으로 위를 해결한다.
첫번째 방법
Object 배열을 생성한 다음, 제네릭으로 형변환한다.
public Stack(){
// 배열 elements 는 push(E)로 넘어온 E 인스턴스만 담는다.
// 따라서 타입 안전성을 보장하지만,
// 이 배열의 런타임 타입은 E[]가 아닌 Object[]다!
@SuppressWarnings("unchecked")
elements = (E[]) new Object [DEFAULT_INITIAL_CAPACITY];
}
장점
가독성 더 좋음
오직 E 인스턴스만 배열로 받음을 선언
코드 더 짧음
형번환을 배열 생성시 단 한번만 하면 됨
두번째 방법
elements 필드의 타입을 E[]에서 Object[]로 바꾼다.
public class Stack<E> {
private Object[] elements;
~~~
public E pop(){
if(size == 0){
throw new EmptyStackException();
}
@SuppressWarnings("unchecked")
E result = (E) elements[--size]; // Object를 E로 형변환 하여 리턴
elements[size] = null;
return result;
}
}
장점
힙 오염이 발생하지 않아 이를 더 선호하는 프로그래머들이 있음
타입 매개 변수의 한계
대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다.
ex) Stack
단, 기본타입은 사용할 수 없다.
ex) Stack
확인
OpenJDK 1.8
의 Stack을 직접 확인해 보았다.
public
class Stack<E> extends Vector<E> {
public Stack() {
}
public E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
}
Stack은 Vector
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
protected Object[] elementData; // item 29의 두번째 방법을 사용하였다.
제네릭 클래스로 만들었을 때 생기는 제네릭 배열 생성 오류의 두번째 방법인 elements 필드 타입을 E[]에서 Object[]로 바꾼다
를 사용하였다.
그럼 stack의 push와 pop을 실행할때 호출하는 vector 메서드 들을 보자,
// push
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
// pop
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index]; // E로 직접 형변환 하였다.
}
pop은 push에 비해 더 많은 메서드 들을 호출하는데, peek() -> elementAt() -> elementData() 순으로 호출한다.
그리고 elementData()에서 item28의 두번째 방법을 사용한 것을 확인 할 수 있다.
결론
직접 형변환 보다 제네릭 타입이 더 안전하고 쓰기 편하다.
기존 타입 중 제네릭이었어야 하는 것이 있다면 제네릭 타입으로 변경하자.
출처
Joshua Bloch. Effective Java 3/E. n.p.: 인사이트, 2018년 11월 1일.