제네릭(generic)은 자바 5부터 사용할 수 있다. 제네릭을 지원하기 전에는 컬렉션에서 객체를 꺼낼 때마다 형변환을 해야 했다. 그래서 누군가 실수로 엉뚱한 타입의 객체를 넣어두면 런타임에 형변환 오류가 나곤 했다. 반면, 제네릭을 사용하면 컬렉션이 담을 수 있는 타입을 컴파일러에 알려주게 된다. 그래서 컴파일러는 알아서 형변환 코드를 추가할 수 있게 되고, 엉뚱한 타입의 객체를 넣으려는 시도를 컴파일 과정에서 차단하여 더 안전하고 명확한 프로그램을 만들어준다. 꼭 컬렉션이 아니더라도 이러한 이점을 누릴 수 있으나, 코드가 복잡해진다는 단점이 따라온다. 이번 장에서는 제네릭의 이점을 최대로 살리고 단점을 최소화하는 방법을 이야기한다.
컴파일러는 이 프로그램이 안전한지 증명할 방법은 없지만, 우리는 할 수 있는 한 이 비검사 형변환이 프로그램의 타입 안전성을 해치지 않는지 스스로 확인해야합니다.
비검사 형변환이 안전하다는 걸 확인했다면 범위를 최소로 좁혀 @SuppressWarnings("unchecked")를 이용하여 해당 경고를 숨깁시다.
1
2
3
4
5
6
7
// 배열 elements는 push(E)로 넘어온 E 인스턴스만 담는다.
// 따라서 타입 안전성을 보장하지만
// 이 배열의 런타임 타입은 E[]가 아닌 Object[]입니다.
@SuppressWarnings("unchecked")publicStack(){elements=(E[])newObject[DEFAULT_INITIAL_CAPACITY];}
위 방법 말고 다른 방식으로는 elements 필드의 타입을 E[]에서 Object[]로 바꾸는 것입니다. 이렇게 하면 pop메서드 부분을 다음과 같이 수정해줘야 합니다.
1
Eresult=(E)elements[--size];
E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없습니다. 이번에도 직접 증명하고 경고를 숨길 수 있습니다.
첫 번째 방식은 형변환을 배열 생성시 단 한 번만 해주면 되지만, 두 번째 방식에서는 배열에서 원소를 읽을 때마다 해줘야하므로 첫 번째 방식이 더 자주 사용됩니다. 하지만
(E가 Object가 아닌 한) 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염을 일으킵니다.
사실 제네릭 타입 안에서 리스트를 사용하는 게 항상 가능하더라도, 꼭 더 좋은 건 아닙니다. 자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList같은 제네릭 타입도 결국은 기본 타입인 배열을 사용해 구현해야 합니다. 또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 합니다.