티스토리 뷰
//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 = elements[--size];
elements[size] = null;
return result;
}
//..생략
}
위 코드는 클라이언트에서 스택에서 꺼낸 객체를 형변환해야하고, 이는 런타임 오류로 이어질 가능성이 있다.
그래서 제네릭으로 바꾸는 것이 좋다.
제네릭으로 바꾸는 방법
1) 클래스 선언에 타입 매개변수 추가
2) 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]; // Error
}
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;
}
//..생략
}
위 코드는 컴파일 오류가 발생한다. 이는 아이템 28에서도 말한 내용이다.
E와 같은 실체화 불가한 타입으로는 배열을 만들 수 없다.
만약 아래와 같이 Object 배열로 바꾼다음 제네릭 배열로 형변환하도록 바꾼다면?
elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY];
컴파일 오류는 나지 않고 경고를 보낸다.
실행은 할 수 있지만, 타입 안전하지 않아서 런타임 오류가 날 수도 있는 위험이 있다.
해결 방안 (비검사 형변환)
1)
그런데 문제의 배열 elements 는 private 필드에 저장되고 클라이언트로 반환되거나 다른 메서드에 전달될 일이 없기 때문에 타입 안전성을 해치지 않음을 스스로 확인할 수 있다. (비검사 형변환)
비검사 형변환이 안전함을 직접 증명했다면 최소 범위로 좁힌 다음 @SuppressWarnings 애너테이션으로
경고를 숨기도록 하자. 이 방법은 가독성이 좋고 형변환을 배열 생성시 단 한번만 하면됨
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
2) elements 필드의 타입을 Object[]로
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
//puah 에서 E 타입만 허용하므로 이 형변환은 안전
@SuppressWarnings("unchecked") E result = (E) elements[--size];
elements[size] = null;
return result;
}
(E)로 형변환할때 경고가 뜬다. (E는 실체화 불가 타입이므로)
그러나 push 에서 E 타입만 허용하기 때문에 이 형변환은 안전하다는 것을 알 수 있다.
그래서 최소 범위인 할당문에서만 애너테이션을 붙여주어 경고를 숨겨주었다.
이 방법은 배열에서 원소를 읽을 때마다 형변환을 해줘야하는 불편함이 있다.
의문
아이템28에서 배열보다는 리스트를 쓰라는 말이 있었다.
그러나 제네릭 타입 안에서 리스트를 사용하는게 항상 가능한것도 아니고 꼭 더 좋은것도 아니다.
결국 ArrayList 같은 제네릭 타입도 기본 타입인 배열을 사용하여 구현해야한다.
제네릭 타입
- 타입 매개변수에 아무 제약도 두지 않는다 (but 기본 타입 사용 불가 → 박싱된 기본 타입 사용 가능)
- 제약을 두는 제네릭 타입 → 는 Delayed의 하위 타입만 받는다는 뜻이다.
이는 클라이언트가 형변환 없이 사용할 수 있어서 ClassCastException은 걱정안해도 된다. - 모든 타입은 자기 자신의 하위 타입이기 때문에 DelayQueue로도 사용 가능
결론
클라이언트에서 직접 형변환해야하는 타입보다 제네릭 타입이 안전하고 편하다.
기존 타입 중에 제네릭이어야하는게 있다면, 변경하자.
기존 클라이언트에는 아무 영향도 주지 않으면서 사용자를 편하게 한다.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] 31.한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.03.23 |
---|---|
[Effective Java] 30.이왕이면 제네릭 메서드로 만들라 (0) | 2022.03.23 |
[Effective Java] 28.배열보다는 리스트를 사용하라 (0) | 2022.03.23 |
[Effective Java] 27.비검사 경고를 제거하라 (0) | 2022.03.18 |
[Effective Java] 26.로 타입은 사용하지 말라 (0) | 2022.03.18 |