Item29. 이왕이면 Generic Type으로 만들어라

Generic Type 변환

Stack Generic Type으로 변경할 경우 , Client에서 Stack에서 꺼낸 객체를 형변환할 필요가 없으며 동시에 ClassCastException 이 나지 않는 장점을 갖는다.

Item 28 에서는 Generic 사용시 배열보다 리스트를 고려하라고 권고는 하였으나 Generic type 안에서 List를 사용하는게 항상 가능하지도 않고, Java가 기본 타입으로 List를 제공하지 않으므로 성능 향상 목적으로 기본 타입인 배열을 사용하기도 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;
}

public boolean isEmpty(){
return size == 0;
}

private void ensureCapacity() {
if(elements.length == size ){
elements = Arrays.copyOf(elements,2*size+1);
}
}


}

Stack class 를 Generic type으로 바꾸기 위해서는 먼저 타입매개변수를 추가한다 (관례상으로 E를 주로 사용함)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class Stack<E> {

private E[] elements;

private int size = 0;

private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {
// compile Error , E는 실체화불가 타입
elements = new E[DEFAULT_INITIAL_CAPACITY];
}

public void push(E e){
ensureCapacity();
elements[size++] = e;
}

public E po public boolean isEmpty(){
return size == 0;
}

private void ensureCapacity() {
if(elements.length == size ){
elements = Arrays.copyOf(elements,2*size+1);
}
}


}

타입매개변수 자체는 실체화 불가 타입임으로 객체화할 수 없다.

1
E[] elements = new E[DEFAULT_INITIAL_CAPACITY]; // compile Error

이를 우회하는 해결방안은 2가지가 있다.

  1. Object[] 타입으로 배열을 먼저 생성하고 , 타입매개변수 타입으로 형변환하는 방법

    1
    E[] elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];

    이떄 타입매개변수 타입으로 변환했을떄 타입 안전한지 확인해야 한다, 해당 배열에서 원소를 꺼내거나, 집어넣을떄 method를 확인하면 원소의 타입은 항상 E임으로 타입 안전하다.

  2. 배열 필드 타입을 Object[] 타입으로 선언하고, 해당 배열에 값을 뺼떄 타입매개변수 타입으로 형변환하는 방법

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Stack<E> {

    private Object[] elements;

    private int size = 0;

    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    //...
    public E pop(){
    if(size == 0){
    throw new EmptyStackException();
    }
    E result = (E) elements[--size]; // 타입 매개변수 타입으로 형변환
    elements[size] = null; // 다 쓴 참조 해제
    return result;
    }

두 방법은 각각의 장단점을 가지고 있다.

1번 방법인 “생성자에서 Object[] 을 타입매개변수 타입으로 형변환하는 방법”은 코드가 짧아져 가독성이 좋다. 또한 배열을 최초로 생성하는 시점에만 한번의 형변환이 일어난다.

반면 2번 방법인 “Object[] 에서 원소를 반환할떄마다 타입매개변수 타입으로 형변환하는 방법” 은 형변환이 pop method 호출 시마다 일어난다는 단점을 가지고 있다.

그럼에도 2번 방법을 고려하는 이유는 1번 방법에서 생길 수 있는 Heap pollution 떄문이다.

heap pollution is a situation that arises when a variable of a parameterized type refers to an object that is not of that parameterized type. This situation is normally detected during compilation and indicated with an unchecked warning. Later, during runtime heap pollution will often cause a ClassCastException.
(ref-https://en.wikipedia.org/wiki/Heap_pollution)

heap pollution이란 선언된 매개변수 타입의 변수로 다른 매개변수 타입의 변수를 가르키는 경우에 발생한다. heap pollution이 발생한 이유는 item 26에서 사용하지 말라고 강조했던 raw type 을 사용했기 때문이다.

앞선 item 설명대로 generic 은 불공변임으로 서로 다른 타입은 어떠한 상속관계도 갖지 않음에도 불구하고, raw type을 사용함으로 다른 타입을 형변환되었다. 따라서 runtime에 원소를 꺼낼떄 ClassCastException이 발생한다.

1
2
3
4
5
public static void main(String[] args) {
List integerList = List.of(10, 11, 12); // raw type 사용
List<String> stringlist = (List<String>) integerList; //heap pollution
String s = stringlist.get(0);// ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
}

한정적 타입 매개변수 - 타입 매개변수 제약

한정적 타입 매개 변수(bounded type pararmeter)는 타입 매개 변수에 제약을 두는 방법으로 , 특정 타입을 상속받아, 해당 타입의 하위 타입만 타입매개변수로 사용가능하도록 제한하는 방법이다.

1
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {//...}

정리

client에서 직접 형변환하지 않도록 Generic 으로 만들어두면 안전하고 사용하기 편하다, 배열의 경우 Generic으로 만들떄는 타입 매개변수는 실체화 불가능한 타입임으로 new Object[] 로 먼저 객체를 생성하고 타입매개변수 타입으로 형변환하면 된다. (heap pollution은 주의)

Comments