이번장은 제네릭 타입을 만드는 방법에 대해 소개한다.
먼저 아이템7. 다 쓴 객체 참조를 해제하라에서 다룬 스택 코드를 살펴보자.
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;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2*size+1);
}
}
이 코드는 원래 제네릭 타입인 것이 좋다.
지금 상태에서는 클라이언트는 스택에서 값을 꺼낼 때 마다 객체를 형변환해야 한다. 타입 세이프하지 않을 수 있어 런타임 오류가 날 수 있다.
위 일반 클래스를 제네릭 클래스로 만들어 보자.
1) 클래스 선언 타입에 매개변수를 추가
2) Object를 적절한 타입 매개변수로 바꾸기
class StackGeneric<E> { //1) 클래스 선언 타입에 매개변수를 추가
private E[] elements; //2) Object를 적절한 타입 매개변수로 바꾸기
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//2) Object를 적절한 타입 매개변수로 바꾸기
public StackGeneric() {
elements = new E[DEFAULT_INITIAL_CAPACITY];
} // 컴파일 에러(java: generic array creation)
public void push(E e){ //2) Object를 적절한 타입 매개변수로 바꾸기
ensureCapacity();
elements[size++] = e;
}
public E pop(){ //2) Object를 적절한 타입 매개변수로 바꾸기
if(size == 0)
throw new EmptyStackException();
E result = elements[--size]; //2) Object를 적절한 타입 매개변수로 바꾸기
elements[size] = null;
return result;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2*size+1);
}
}
생성자 코드에서 아래와 같은 컴파일 에러가 발생한다.
public StackGeneric() {
elements = new E[DEFAULT_INITIAL_CAPACITY];
}// 컴파일 에러(java: generic array creation)
해결방법
1. 제네릭 배열 생성을 금지하는 제약을 우회
public StackGeneric() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
} // 경고: uses unchecked or unsafe operations.
uses unchecked or unsafe operations.라는 경고가 발생한다.
아이템27.비검사 경고(unchecked warning)를 제거하라에서 배운대로 이 비검사 형변환이 타입 safe하다는 것을 확인하고 경고를 숨겨야 한다.
-> 배열 elements는 private필드에 저장되고, 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없다.
-> push메서드를 통해 배열에 저장되는 원소의 타입은 항상 E이다
-> 따라서 이 비검사 형변환은 안전하다.
@SuppressWarnings("unchecked")
public StackGeneric() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
위처럼 @SuppressWaraings를 달아 경고를 무시한다.
2. elements필드 타입을 E[] -> Object[]로 바꾼다.
class StackGeneric2<E> { //1) 클래스 선언 타입에 매개변수를 추가
private Object[] elements; //2) Object를 적절한 타입 매개변수로 바꾸기
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//2) Object를 적절한 타입 매개변수로 바꾸기
@SuppressWarnings("unchecked")
public StackGeneric2() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e){ //2) Object를 적절한 타입 매개변수로 바꾸기
ensureCapacity();
elements[size++] = e;
}
public E pop(){ //2) Object를 적절한 타입 매개변수로 바꾸기
if(size == 0)
throw new EmptyStackException();
E result = elements[--size]; //2) Object를 적절한 타입 매개변수로 바꾸기
elements[size] = null;
return result;
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2*size+1);
}
}
1에서 elements 선언시 필드타입을 Object로 바꿔준다.
public E pop(){
if(size == 0)
throw new EmptyStackException();
E result = elements[--size]; //컴파일 에러: java: incompatible types: java.lang.Object cannot be converted to E
elements[size] = null;
return result;
}
그럼 위 부분에서 컴파일 에러가 발생한다.
아래처럼 배열이 반환한 원소를 E로 형변환하면 오류 대신 경고가 발생.
public E pop(){
if(size == 0)
throw new EmptyStackException();
E result = (E) elements[--size]; // uses unchecked or unsafe operations.
elements[size] = null;
return result;
}
이 역시 @SuppressWarnings를 붙여 해결한다.
@SuppressWarnings("unchecked")
E result = (E) elements[--size]; // uses unchecked or unsafe operations.
제너릭 배열 생성을 제거하는 두 방법 모두 장단점이 있다.
1번. 제네릭 배열 생성을 금지하는 제약을 우회
- 가독성이 더 좋다. 배열의 타입을 E[]로 선언해 오직 E타입 인스턴스만 받음을 표현한다.
- 형변환을 배열 생성 시 한 번만 해주면 된다.
- 단점) 배열의 런타임 타입이 컴파일 타입과 달라 힘 오션(아이템32)을 일으킨다.
2번. elements필드 타입을 E[] -> Object[]로 바꾼다.
- 힙 오염을 고려하지 않아도 된다.
- 단점) 배열에서 원소를 읽을 때마다 형변환을 해줘야 한다.
'Java' 카테고리의 다른 글
[Effective Java] 아이템31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.05.25 |
---|---|
[Effective Java] 아이템30. 이왕이면 제네릭 메서드로 만들라 (0) | 2022.05.22 |
[Effective Java] 아이템 28. 배열보다는 리스트를 사용하라 (0) | 2022.05.08 |
[Effective Java] 아이템 27. 비검사 경고(unchecked warning)를 제거하라 (0) | 2022.05.08 |
[Effective Java] 아이템 26. Raw타입은 사용하지 말라. (0) | 2022.05.08 |