클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다. 그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라. 그렇게 하려면 제네릭 타입으로 만들어야 할 경우가 많다. 기존 타입 중 제네릭이었어야 하는 게 있다면 제네릭 타입으로 변경하자. 기존 클라이언트에는 아무 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다.
Object 기반 스택 → 제네릭으로 바꾸기배열을 제네릭 타입으로 만드는 방법1. Object 배열을 만든 다음 E[] 로 형변환 하기2. elements 필드의 타입을 E[] → Object[] 로 바꾸기
Object 기반 스택 → 제네릭으로 바꾸기
package effectivejava.chapter2.item7; import java.util.*; // 코드 7-1 메모리 누수가 일어나는 위치는 어디인가? (36쪽) 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; } /** * 원소를 위한 공간을 적어도 하나 이상 확보한다. * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다. */ private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } // 코드 7-2 제대로 구현한 pop 메서드 (37쪽) public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 다 쓴 참조 해제 return result; } public static void main(String[] args) { Stack stack = new Stack(); for (String arg : args) stack.push(arg); while (true) System.err.println(stack.pop()); } }
배열을 제네릭 타입으로 만드는 방법
1 | 2 |
가독성이 더 좋음 | ㅤ |
배열의 타입을 E[]로 선언하여 오직 E타입 인스턴스만 받음을 확실히 어필 | ㅤ |
형변환을 배열 생성 시 단 한번만 해주면 됨 → 현업에서 더 선호함 | 배열에서 원소를 읽을 때마다 해줘야 함 |
하지만 E가 Object가 아닌 한 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염(heap polltion; 아이템32)을 일으킨다. | ㅤ |
1. Object 배열을 만든 다음 E[] 로 형변환 하기
- 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법
- 컴파일러가 오류 대신 경고를 내보내고 이렇게 할 수는 있지만 (일반적으로) 타입 안전하지는 않음
- 컴파일러는 이 프로그램이 타입 안전한지 증명할 방법이 없지만 우리는 할 수 있음
// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다. // 따라서 타입 안전성을 보장하지만, // 이 배열의 런타임 타입은 E[] 가 아닌 Object[]다! @SuppressWarnings("unchecked") public Stack() { elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY]; }
2. elements 필드의 타입을 E[] → Object[] 로 바꾸기
- 이렇게 하면 배열이 반환한 원소를 E로 형변환 해야 하는데, 이 때 또 경고가 뜨게 됨
E result = (E) 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; }
아이템 28의 배열보다는 리스트를 우선하라 아이템과 모순돼 보일 수 있음. 사실 제네릭 타입 안에서 리스트를 사용하는 게 항상 가능하지도, 꼭 더 좋은 것도 아님. 자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국은 기본 타입인 배열을 사용해 구현해야 함.