Item 29 - 이왕이면 제네릭 타입으로 만들라
이왕이면 제네릭 타입으로 만들라
Intro
JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다.
제네릭이 필요한 Object 기반 스택
해당 클래스는 원래 제네릭 타입이어야 한다.
클라이언트는 스택에서 꺼낸 객체를 형변환해야 하는데, 이때 런타임 오류가 날 위험이 있다.
일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다.
스택이 담을 원소의 타입 하나만 추가하면 된다.
이때 타입 이름으로는 보통 E를 사용한다. 아이템 68
E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.
배열을 사용하는 코드를 제네릭으로 만들 때 문제가 발생하게 되고, 적절한 해결책은 두 가지가 있다.
첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법
Object 배열을 생성한 다음 제네릭 배열로 형변환 한다.
컴파일러는 오류 대신 경고를 내보낼 것이다.
타입 안전하지 않다는 경고
비검사 형변환이 프로그램의 타입 안전성을 해치지 않음 스스로 확인해야 한다.
비검사 형변환이 안전함을 직접 증명했다면 범위를 최소로 좁혀 @SuppressWarnings 어노테이션으로 해당 경고를 숨긴다. 아이템 27
생성자가 비검사 배열 생성 말고는 하는 일이 없으니 생성자 전체에서 경고를 숨겨도 좋다.
어노테이션을 달면 Stack을 깔끔하게 컴파일 되고, 명시적으로 형변환하지 않아도 ClassCastException 걱정 없이 사용할 수 있게 된다.
두 번째는 elements 필드의 타입을 E[]에서 Object[]로 바꾸는 방법
첫 번째와는 다른 오류가 발생한다.
배열이 반환한 원소를 E로 형변환하면 오류 대신 경고가 뜬다.
E는 실체화 불가 타입이므로 컴파일러는 런타임에 이뤄지는 형변환이 안전한지 증명할 방법이 없다.
pop 메서드 전체에서 경고를 숨기지 말고, 비검사 형변환을 수행하는 할당문에서만 숨긴다.
제네릭 배열 생성을 제거하는 두 방법 모두 나름의 지지를 얻고 있다.
첫 번째 방법은 가독성이 더 좋다.
배열의 타입을 E[]로 선언하여 오직 E 타입 인스턴스만 받음을 확실히 어필한다.
형변환을 배열 생성 시 단 한번만 해주면 된다.
두 번째 방식에서는 배열에서 원소를 읽을 때마다 해줘야 한다.
따라서 현업에서는 첫 번째 방식을 더 선호하며 자주 사용한다.
하지만 배열의 런타임 타입이 컴파일 타임 타입과 달라 힙 오염(heap pollution)을 일으킨다. 아이템 32
힙 오염이 마음에 걸리는 프로그래머는 두 번째 방식을 고수하기도 한다.
제네릭 Stack 을 사용하는 예시
명령줄 인수들을 역순으로 바꿔 대문자로 출력하는 프로그램
Stack 에서 꺼낸 원소에서 String의 toUpperCase 메서드를 호출할 때 명시적 형변환을 수행하지 않는다.
(컴파일러에 의해 자동 생성)
형변환이 항상 성공함을 보장한다.
"배열보다는 리스트를 우선하라"는 아이템 28과 모순되어 보인다.
사실 제네릭 타입 안에서 리스트를 사용하는 게 항상 가능하지도, 꼭 더 좋은 것도 아니다.
자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국 기본 타입인 배열을 사용해 구현해야 한다.
또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다.
Stack 예처럼 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다.
Stack, Stack, Stack>, Stack 등 어떤 참조 타입으로도 Stack을 만들 수 있다.
단, 기본 타입은 사용할 수 없다.
Stack나 Stack을 만들려고 하면 컴파일 오류가 난다.
이는 자바 제네릭 타입 시스템의 근본적인 문제이나, 박싱된 기본타입을 사용해 우회할 수 있다. 아이템 61
타입 매개변수에 제약을 두는 제네릭 타입도 있다.
java.util.current.DelayQueue
타입 매개변수 목록인 는 java.util.concurrent.Delayed 의 하위 타입만 받는다는 것이다.
DelayQueue 자신과 DelayQueue를 사용하는 클라이언트는 DelayQueue의 원소에서 곧바로 Delayed 클래스의 메서드를 호출할 수 있다.
ClassCastException 걱정은 할 필요가 없다.
이러한 타입 매개변수 E를 한정적 타입 매개변수(bounded type parameter)라 한다.
모든 타입은 자기 자신의 하위 타입이므로 DelayQueue로도 사용할 수 있음을 기억해야 한다.
핵심 정리
클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.
새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라.
그런 경우 제네릭 타입으로 만들어야 하는 경우가 많다.
기존 타입 중 제네릭이었어야 하는 부분이 있다면 제네릭 타입으로 변경하자.
기존 클라이언트에는 아무런 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다. 아이템 26
Last updated