소주캉
- 클라이언트에서 직접 형변환해야 하는 타입이 있다면
제네릭 타입
으로 만들자. - 클라이언트가 형변환 없이 사용할 수 있다면 더 안전하고 쓰기 편해진다.
- 임의의 타입을 받을 수 있게
Object의 배열
로 구현한stack
예제
public class StackTest {
private Object[] elements; // Object의 배열로 구현
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public StackTest() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
- 코드에 오류는 발생하지 않는다. 하지만 클라이언트가 사용할 경우
명시적 형변환
을 수행해주어야 한다. - 형변환이 적절하게 이뤄지지 않을 경우
Runtime오류
가 발생한다.
public static void main(String[] args) {
StackTest stack = new StackTest();
stack.push(5);
int element = (int)stack.pop(); // 클라이언트가 직접 형변환.
// 잠재적 런타임 오류의 위험이 있다.
}
제네릭 타입
으로 바꿀 경우 타입 오류를Compile타임
에 잡아낼 수 있다.- 클라이언트가 따로 형변환 해줄 필요가 없어 사용하기 쉽고 안전하다.
public class GenericStackTest<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public GenericStackTest() {
this.elements = new E[DEFAULT_INITIAL_CAPACITY]; // 실체화 불가 타입 에러
}
public void push(E e) {
elements[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
E result = elements[--size];
elements[size] = null;
return result;
}
List
,제네릭 타입 변수
등실체화 불가 타입
은 배열을 만들 수 없기 때문에컴파일 오류
가 발생한다.
실체화 불가 타입이란?
제네릭의 `타입 정보`가 런타임에는 소거(erasure)되어 원소 타입을 `컴파일타임`에만 검사하며 `런타임`에는 알 수 없다. 실체화 되지 않아서 런타임에는 컴파일 타임보다 타입 정보를 적게 가지는 타입이다.- 배열은
Object[]
로 생성하고 이를 타입변수E[]
로 형변환 한다. 런타임에서 지정한 타입의 배열로 바뀐다(ex:int[]
) - 컴파일 오류 대신
경고
를 내보낸다. - 비검사 형변환이 타입 안전한지 개발자가 스스로 증명해야한다.
- 안전함을 확신했다면 경고를 숨긴다.
@SuppressWarnings("unchecked")
public GenericStackTest() {
this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
- 첫 번째 방법이 가독성이 더 좋고 코드가 더 짧아 현업에서 선호된다.
- 그러나 배열의
런타임 타입: E[]
이컴파일 타입 Object[]
과 달라힙 오염
을 일으킨다.
힙 오염이란?
실체화 불가 타입은 런타임에서 타입 정보가 소멸되므로 제네릭 배열의 런타임 타입이 컴파일 타임의 타입과 다르게 된다. 이처럼 매개변수화 타입의 변수가 타입이 다른 객체를 참조하는 것을 힙 오염이라 한다.elements
필드의 타입을E[]
에서Object[]
로 변경- 힙오염에서 안전하다.
- 첫 번째 방식에서는 형변환을 배열 생성시
단 한번
만 한다. - 두 번째 방식은 배열에서 원소를
읽을 때마다 형변환
해줘야 한다.
public class GenericStackTest<E> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public GenericStackTest() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
elements[size++] = e;
}
public E pop() {
if (size == 0) {
throw new EmptyStackException();
}
@SuppressWarnings("unchecked") E result = (E) elements[--size];
// 원소를 읽을 때마다 형변환을 해주고 경고를 무시한다.
elements[size] = null;
return result;
}
- 명시적으로 타입을 받으므로 컴파일 타임에 오류를 감지할 수 있다.
- 클라이언트가 직접 형변환 해줄 필요가 없으므로 사용하기 쉽고 안전하다.
public static void main(String[] args) {
GenericStackTest<Integer> stack = new GenericStackTest<>();
stack.push(5);
int element = stack.pop(); // (int)형변환 필요 없음
}