-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
📌 [아이템 29] 이왕이면 제네릭 타입으로 만들라
✨ 핵심 내용
제네릭 타입 만들기
제네릭 타입은 만들기는 어렵지만 만들면 매우 유용
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);
}
}- 해당 클래스는 스택에서 꺼낸 객체를 알맞게 형변환 해야함
- 런타임 오류가 날 위험이 있다 → 제네릭의 사용 의의
제네릭으로 변환
첫 단계는 클래스 선언에 타입 매개 변수 추가
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackO {
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 메서드는 그대로다
}이 때 오류가 발생하는데
Stack.java:8: generic array creation
elements = new E[DEFAULT_INITIAL_CAPACITY];E와 같은 실체화 불가 타입으로 배열을 만들 경우 생기는 오류이다.
이를 해결하는 방법은 두가지가 있다.
- 제네릭 배열 생성을 금지하는 제약을 우회
- Object 배열을 생성한 다음 제네릭 배열로 형변환
- 명시적 형변환이므로 타입이 안전하지 않음 → 검사해봐야 함
Stack.java:8: warning: [unchecked] unchecked cast
found: Object[], required: E[]
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];- elements 배열은 private 필드에 저장되고, 클라이언트로 반환되거나 다른 메서드에 저장되지 않음
- Push 메서드를 통해 배열에 저장되는 원소의 타입은 항상 E
따라서 해당 비검사 형변환은 안전 → @SuppressWarnings 으로 경고를 숨기자
// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.
// 따라서 타입 안전성을 보장하지만,
// 이 배열의 런타임 타입은 E[]가 아닌 Object[]다!
QSuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}- elements 필드의 타입을 E[]에서 Object[]로 변경
Stack.java:19: incompatible types
found: Object, required: E
E result = (E) elements[--size];- E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 알 수 없음
- 개발자가 검사해야함
- pop 메서드 전체에서 경고를 숨기지 않고, 아이템 27의 조언에 따라 비검사 형변환 수행 할당문만 숨김
// 비검사 경고를 적절히 숨긴다
public E pop() {
if (size = 0)
throw new EmptyStackException();
// push에서 E 타입만 허용하므로 이 형변환은 안전하다.
@SuppressWarnings("unchecked") E result = (E) elements[—size];
elements [size] = null; // 다 쓴 참조 해제
return result;
}첫번째는 가독성이 좋지만 배열의 런타임 타입이 컴파일타입과 달라 힙 오염을 일으킴
두번째는 배열에서 원소를 꺼낼때 마다 형변환을 해야함
타입 매개변수
위 Stack의 예는 아이템 28 배열보다는 리스트를 우선하라 와는 모순돼 보임
사실 자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList같은 제네릭 타입도 기본 타입인 배열을 사용해 구현한다.
또한 HashMap과 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 함
대다수의 제네릭 타입은 타입 매개변수에 아무런 제약이 없지만 기본타입은 사용할 수 없다
- 하지만 박싱된 기본 타입으로 우회할 수 있음
타입 매개변수에 제약을 둬 형변환 없이 곧바로 호출할 수 있도록 할 수도 있다
- 한정적 타입 매개변수
class DelayQueue<E extends Delayed> implements BlockingQueue<E>- 이 클래스에 매개변수로 들어올 수 있는 모든 타입은 자기 자신의 하위 타입이므로
- DelayQueue로도 사용 가능
정리
직접 형변환 해야 한다면 제네릭 타입을 사용해 형변환 없이도 사용할 수 있도록 하자
그렇기 위해 위 두 방법으로 제네릭 타입을 만들자
💡 새롭게 알게 된 점
📚 정리
두 가지 방법의 차이점
- Object 배열을 생성하고 E 배열로 형변환 ((E[]) new Object[size])
- 장점: 배열을 한 번만 생성하면 되므로 성능 면에서 유리
- 단점: 배열의 런타임 타입은 E[]가 아닌 Object[]라서 힙 오염(Heap Pollution) 가능성이 있음
- 배열은 Object[]로 유지하고 요소를 꺼낼 때마다 E로 형변환 ((E) elements[i])
- 장점: 힙 오염을 방지할 수 있음
- 단점: 매번 요소를 꺼낼 때마다 형변환 비용 발생
📢 댓글로 각자의 학습 내용을 공유해주세요!
Reactions are currently unavailable