요약
클라이언트에서 따로 형변환 할 필요없이 편리하게 사용할 수 있게 하기 위해 웬만하면 클래스를 제네릭 타입으로 만들어라!
일반 클래스 → 제네릭 클래스
1. Class<E>처럼 클래스 선언에 타입 매개변수(E) 추가
- Stack 클래스를 Stack<E>바꾸고, Object 타입을 E로 바꾸기
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]; // E[]에서 컴파일 에러 발생
}
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;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
2. 실체화 불가 타입 E로 배열 선언하는 2가지 방법
E는 실체화 불가 타입이므로 배열로 선언할 수 없는데, 이를 해결할 수 있는 2가지 방법이 있다.
방법1 : 제네릭 배열 생성 제약을 우회하는 방법
- Object 배열 → 제네릭 배열
- 컴파일 에러는 발생하지 않지만, 타입이 불안정해 경고가 발생한다
- 타입 불안정문제 해결하기
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
- 이 경우에는 타입이 안정한지 증명
- Stack<E>의 경우 elements는 private 필드
- 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 아예 없다.
- @SuppressWarnings로 경고를 제거해주면 된다
- 장점
- 가독성이 좋고 코드 길이가 짧다.
- 형변환은 배열 생성시 한 버난 하면 된다.
- 단점
- E가 Object가 아닌 한 배열의 런타임 타입이 컴파일 타입과 달라 힙 오염이 발생할 수 있다.
방법2 : elements는 Object 타입으로 두고, result 타입을 E로 변경하기
1. result의 타입 Object → E
public E pop() {
..
E result = (E) elements[--size]; // Object -> E
...
}
E는 실체화 불가 타입이므로 컴파일러는 런타임에서 이뤄지는 형변환이 안전한지 증명할 길이 없다. 이 경우에도 타입 안정성을 직접 증명하고, 경고를 숨기면 된다. 경고는 비검사 형변환을 수행하는 할당문에서만 숨긴다.
2. @SuppressWarnings("unchecked") 선언
public E pop() {
...
@SuppressWarnings("unchecked") E result = (E) elements[--size];
...
}
elements 타입에는 E 타입만 들어오기 때문에 위 형변환은 안전하므로 @SuppressWarnings 로 경고를 제거한다.
- 장점
- 힙 오염 걱정을 안 해도 됨
- 단점
- 형변환을 배열에서 원소를 읽을 때마다 해줘야 함
제테릭 타입 특징
- 타입 매개변수에 거의 제약이 없다
- Stack<Object>, Stack<int[]>, Stack<List<String>>, Stack 등 가능
- 타입 매개변수에 기본 타입은 안됨
- Stack<int>, Stack<double> → xxx
- 타입 매개변수에 제약 설정 가능함(한정적 타입 매개변수)
- DelayQueue<E extends Delayed>
힙 오염
매개변수 유형이 서로 다른 타입을 참조할 때 발생하는 문제이다.
컴파일은 되지만 런타임에 문제가 발생하게 된다(ClassCastException).
ArrayList<String> list1 = new ArrayList<>();
list1.add("홍길동");
list1.add("임꺾정");
// 로직 수행...
Object obj = list1; -> String에서 Object로 업캐스팅
// 로직 수행...
ArrayList<Double> list2 = (ArrayList<Double>) obj;
list2.add(1.0);
list2.add(2.0);
System.out.println(list2); // [홍길동, 임꺾정, 1.0, 2.0]
for(double n : list2) {
System.out.println(n);
}
Reference
'Dev Language > EffectiveJava' 카테고리의 다른 글
[EffectiveJava] int 상수 대신 열거 타입(Enum)을 사용하라 (2) | 2025.01.18 |
---|---|
[EffectiveJava] 이왕이면 제네릭 메서드로 만들라 (0) | 2025.01.11 |
[EffectiveJava] 로 타입(raw type)은 사용하지 말라 (4) | 2024.12.29 |
[EffectiveJava] 멤버 클래스는 되도록 static으로 만들라 (0) | 2024.12.22 |
[EffectiveJava] 인터페이스는 구현하는 쪽을 생각해 설계하라 (1) | 2024.12.08 |