코딩못하는사람

아이템 29]이왕이면 제네릭 타입으로 만들라 본문

자바 메모장/이펙티브 자바

아이템 29]이왕이면 제네릭 타입으로 만들라

공부절대안함 2021. 7. 11. 13:57

아이템29] 이왕이면 제네릭 타입으로 만들라

제네릭(generic)이란?

데이터의 타입(data type)을 일반화한다(generalize)는 것을 의미한다.

클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법이다.

이렇게 컴파일 시에 타입검사를 미리 수행하면 타입 안정성과 타입 검사에 들어가는 노력을 줄일 수 있다.

 

클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.

따라서 새로운 타입을 설계할 때 형변환 없이 제네릭 타입을 사용해주자.

 

제네릭으로의 변경이 필요한 코드를 보고 제네릭으로 고쳐보면서 이해해보자.

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);
    }
}

 

 

 일반 클래스에서 제네릭 클래스로 가기위해서는 타입 매개변수를 추가해준다.Object를 적절한 타입 매개변수로 바꿔주자

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;
    }
    ... // isEmpty와 ensureCapacity 메서드는 그대로다. 
}

다음과 같이 변경했을 때 컴파일 오류가 발생한다. E와 같은 실체화 불가 타입으로는 배열을 만들 수 없기 때문이다.

배열을 제네릭 바꾸기 위해서는 두가지 해결책을 사용하자.

1.제네릭 배열생성을 금지하는 제약을 우회하는 방법

Object배열을 생성한 후 제네릭으로 형변환 해주면 해결가능하다.

// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다. 
// 따라서 타입 안정성을 보장하지만, 
// 이 배열의 런타임 타입은 E[]가 아닌 Object[]다! 
@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

컴파일러는 이 프로그램이 타입 안전한지 증명할 수 없어서 우리 스스로 증명해야 한다.

 

2.elements 필드의 타입을 E[] 에서 Object[]로 바꾸는 방법.

이번 방식은 배열이 반환한 원소를 형변환 할 때 오류가 발생한다.

Stack.java:19: incompatible types
found: Object, required: E
        E result = elements[--size];
                           ⌃

E는 실체화 불가 타입이므로 런타임에 이뤄지는 형변환이 안전한지 알 수 없다.

이번에도 우리가 직접 증명해주고 경고를 숨기면 된다.

// 비검사 경고를 적절히 숨긴다
public E pop() {
    if (size == 0)
        throw new EmptyStackException();

    // push에서 E 타입만 허용하므로 이 형변환은 안전하다. 
    @SuppressWarnings("unchecked") E result = (E) elements[--size];

    elements[size] = null; // 다 쓴 참조 해제 
    return result;
}

 

첫번째 방법은 배열의 타입을 E[]로 선언하여 오직 E타입 인스턴스만 받음을 확신할 수 있다.

형변환을 배열 생성시에만 해주면 된다는 점에서 코드가 깔끔하다는 장점이 있지만 힙 오염(heap pollution)을 일으킨다.

 

두번째 방식은 힙오염의 위험은 없지만 매번 필요할때 마다 형변환을 해주어야 하는 단점이 있다.

 

Comments