Book/Effective Java

[Effective Java]29.이왕이면 제네릭 타입으로 만들라

마닐라 2022. 7. 7. 21:40

아이템 7에서 다룬 Object[]를 사용하는 단순한 스택 코드가 있다.

이 클래스는 원래 제네릭 타입이어야 마땅하다.

 

일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다.

배열을 사용하는 코드를 제네릭으로 만들려 할 때는 E[]로 만들 수 없다.

적절한 해결책은 두 가지다.

첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다.

Object 배열을 생성한다음 제네릭 배열로 형변환하는 것이다.

이렇게 한다면 E[]로 생성할 때와는 다르게 비검사 형변환 경고가 발생한다.

문제의 배열은 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없기에 타입이 안전하다.

 

비검사 형변환이 안전함을 직접 증명했다면 이전 아이템에서 학습했듯이 범위를 최소로 좁혀 @SuppressWarning 애너테이션으로 해당 경고를 숨긴다.

 

두 번째 해결책은 E[] 타입에서 Object[]로 바꾸는 것이다.

그리고 배열이 반환한 원소를 E로 형변환한다.

E는 실체화 불가 타입이므로 위와 마찬가지로 직접 증명하고 경고를 숨길 수 있다.

 

두가지 방법 모두 나름의 지지를 얻고 있다.

첫 번째 방법은 가독성이 더 좋다.

배열의 타입을 E[]로 선언하여 오직 E 타입 인스턴스만 받음을 확실히 어필한다.

첫번째 방식에서는 형변환을 배열 생성시 단 한번만 해주면 되지만, 두 번째 방식에서는 배열에서 원소를 일글 때마다 해줘야 한다.

따라서 현업에서는 첫 번째 방식을 더 선호하며 자주 사용한다.

 

하지만 (E가 Object가 아닌 한) 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염(아이템 32)을 일으킨다.

 

지금까지 설명한 예는 "배열보다는 리스트를 우선하라"는 아이템 28과 모순돼 보인다.

제네릭 타입 안에서 리스트를 사용하는게 항상 가능하지도, 꼭 더 좋은 것도 아니다.

자바가 리스트를 기본 타입으로 제공하지 않으므로 ArrayList 같은 제네릭 타입도 결국은 기본 타입인 배열을 사용해 구현해야한다.

또한 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다.

 

타입 매개변수에 제약을 두는 제네릭 타입도 있다.

예를 들어 <E extends Delayed>는 Delayed의 하위 타입만 받는다는 뜻이다.

따라서 형변환 없이 해당 클래스의 메서드를 호출할 수 있다.

이러한 타입 매개변수 E를 한정적 타입 매개변수라 한다.

 

핵심 정리

클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.

그러니 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하자.

그렇게 하려면 제네릭 타입으로 만들어야할 경우가 많다.

기존 타입 중 제네릭이었어야 하는 게 있다면 제네릭 타입으로 변경하자.

기존 클라이언트에는 아무 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다.