[이펙티브 자바] 아이템 29. 이왕이면 제네릭 타입으로 만들라

June·2022년 3월 16일
0

[이펙티브자바]

목록 보기
27/72

Object 기반 스택 - 제네릭이 필요하다

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 = elemtns[--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);
    }
}

이 클래스에는 제네릭 타입이어야 한다. 지금은 클라이언트가 스택에서 꺼낸 객체를 형변환 해야하는데, 이때 런타임 오류가 날 위험이 있다.

클래스 선언에 타입 매개변수를 추가하면된다.

제네릭 스택 첫 단계 - 컴파일되지 않는다

public class Stack {
    private E[] 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();
        E result = elemtns[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }
    ...
}

아이템 28에 나오는 것처럼 E와 같은 실체화 불가 타입으로 배열 생성 안된다.

방법은 두 가지다.

  • 하나는 Object 배열 생성하고 제네릭 배열로 형변환하는 것. 컴파일 경고가 난다.
@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

배열 elements가 private 필드에 저장되고 클라이언트에 반환되거나 다른 메서드에 전달되지 않아서 완전하긴하다.

가독성이 더 좋다. 배열의 타입을 E[]로 선언하며 E 타입 인스턴스만 받읆을 어필한다. 형변환을 배열 생성시에만 해주면 된다. 하지만 (E가 Object)가 아닌한 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염을 일으킨다.

  • elements 필드의 타입을 E[]에서 Object[]로 변경

E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다.

//이 배열의 런타임 타입은 E[]가 아닌 Object[]다
public E pop() {
    if (size == 0)
        throw new EmptyStackException();
    
    @SuppressWarnings("unchecked")
    E result = (E) elements[--size];
    
    elements[size] = null; // 다 쓴 참조 해제
    return result;
}

컴파일러가 런타임에 이뤄지는 형변환이 안전한지 알 수없다.

  • 형변환을 원소를 읽을때마다 해줘야 한다.

그런데 지금까지 설명은 아이템 28. 배열보다는 리스트를 사용하라과 모순되어보인다.

제네릭 타입 안에서 리스트를 사용하는게 항상 가능하지도 않고, ArrayList도 결국 내부적으로 기본 타입 배열을 쓴다. HashMap도 성능을 높일 목적으로 배열을 쓴다.

결론: 기존 타입 중 제네릭이었어야 하는게 있다면 제네릭 타입으로 변경하자. 기존 클라이언트에는 아무 영향을 주지 않으면서, 새로운 사용자를 편하게 해준다.

0개의 댓글