요약

클라이언트에서 따로 형변환 할 필요없이 편리하게 사용할 수 있게 하기 위해 웬만하면 클래스를 제네릭 타입으로 만들어라!

 

일반 클래스 → 제네릭 클래스

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 : 제네릭 배열 생성 제약을 우회하는 방법

  1. Object 배열 → 제네릭 배열
  2. 컴파일 에러는 발생하지 않지만, 타입이 불안정해 경고가 발생한다
  • 타입 불안정문제 해결하기
@SuppressWarnings("unchecked")
public Stack() {
	elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
  1. 이 경우에는 타입이 안정한지 증명
    1. Stack<E>의 경우 elements는 private 필드
    2. 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 아예 없다.
    → 비검사 형변환은 확실히 안전하다!
  2. @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 로 경고를 제거한다.

  • 장점
    • 힙 오염 걱정을 안 해도 됨
  • 단점
    • 형변환을 배열에서 원소를 읽을 때마다 해줘야 함

 

 

제테릭 타입 특징

  1. 타입 매개변수에 거의 제약이 없다
    1. Stack<Object>, Stack<int[]>, Stack<List<String>>, Stack 등 가능
  2. 타입 매개변수에 기본 타입은 안됨
    1. Stack<int>, Stack<double> → xxx
  3. 타입 매개변수에 제약 설정 가능함(한정적 타입 매개변수)
    1. 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

[제네릭(Generic)] (2) 이것만은 주의해줘

+ Recent posts