Item29 - 이왕이면 제네릭 타입으로 만들라
2023. 12. 6. 20:47ㆍBook/이펙티브 자바
클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.
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);
}
}
public boolean isEmpty() {
return size == 0;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //다 쓴 참조 해제
return result;
}
}
위 Stack 코드 예제를 제네릭으로 바꿔보자
// E[]를 이용한 제네릭 스택 (170-174쪽)
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;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String arg : List.of("a", "b", "c"))
stack.push(arg);
while (!stack.isEmpty())
System.out.println(stack.pop().toUpperCase());
}
}
이 단계에서 오류가 발생하는 부분이 있다.
public Stack() {
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
아이템 28에서 나온 것 처럼 실체화 불가 타입으로는 배열을 만들 수 없다. 배열을 사용하는 코드를 제네릭으로 만들려 할 때는 이 문제가 종종 등장할 것이다.
해결책은 두 가지가 있다.
/* 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.
따라서 타입 안전성을 보장하지만,
이 배열의 런타임 타입은 E[]가 아닌 Object[]다! */
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
1. 제네릭 배열 대신에 Object 배열을 생성한 뒤에 제네릭 배열로 형변환 한다.
- 형변환을 배열 생성시 한 번만 한다.
- 가독성이 좋다.
- 힙 오염이 발생할 수 있다.(아이템32 에서 설명)
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 void push(E e) {
ensureCapacity();
elements[size++] = e;
}
// 코드 29-4 배열을 사용한 코드를 제네릭으로 만드는 방법 2 (173쪽)
// 비검사 경고를 적절히 숨긴다.
public E pop() {
if (size == 0)
throw new EmptyStackException();
// push에서 E 타입만 허용하므로 이 형변환은 안전하다.
@SuppressWarnings("unchecked") 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);
}
// 코드 29-5 제네릭 Stack을 사용하는 맛보기 프로그램 (174쪽)
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String arg : List.of("a", "b", "c"))
stack.push(arg);
while (!stack.isEmpty())
System.out.println(stack.pop().toUpperCase());
}
}
2. 제네릭 배열 대신에 Object 배열을 사용하고, 배열이 반환한 원소를 E로 형변환 한다.
- 원소를 읽을 때 마다 형변환을 해줘야 한다.(성능상 문제가 있을 수 있고 가독성도 좋지 않다.)
'Book > 이펙티브 자바' 카테고리의 다른 글
Item31 - 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2023.12.07 |
---|---|
Item30 - 이왕이면 제네릭 메서드로 만들라 (1) | 2023.12.06 |
Item28 - 배열보다는 리스트를 사용하라 (2) | 2023.12.05 |
Item27 - 비검사 경고를 제거하라 (2) | 2023.12.05 |
Item26 - 로 타입은 사용하지 말라 (0) | 2023.12.04 |