개발을 할 때 클래스 내에서 다른 객체들을 담는 역할을 수행하는 클래스를 생성하는 경우가 많습니다.
이런 경우 중 대표적인 예시로 스택이 있으며 다음 코드는 아이템 7에서 다룬 스택 코드입니다.
위 코드처럼 범용적으로 사용하기 위해 Object 객체를 담는 경우, 제네릭을 사용하면 ClassCastException을 미연에 방지할 수 있어 좋습니다.
- 제네릭을 사용하지 않을 경우, Object 배열 내 어떤 타입의 객체가 들어갈지 미리 알 수 없음
- pop() 메서드에서 스택에서 꺼낸 객체를 형변환할 때, 제네릭을 사용하지 않으면 런타임 오류인 ClassCastException 발생 위험이 존재
정리하자면, 클래스 내에서 다른 객체들을 담는 역할을 수행하는 클래스는 제네릭으로 선언하는 것이 좋습니다.
이에 관련하여 일반 클래스를 제네릭 클래스로 변환하는 방법은 크게 두 가지가 있습니다.
방법 1
첫 번째 방법은 Object 배열을 생성한 뒤에 제네릭 배열로 형변환 하는 방법입니다.
이렇게 할 거면 '애초에 제네릭 배열(E[])를 생성하면 되지 않나?'라고 생각하겠지만 아이템 28에서 언급했다시피 제네릭 배열은 타입 안전성을 보장하지 못하기 때문에 컴파일러에서 컴파일 에러를 발생시킵니다.
첫 번째 방법을 적용하면 Stack 코드가 다음과 같이 바뀝니다.
생성자에 발생하는 비검사 경고는 Object[] 배열 필드가 private으로 선언되어 외부에 노출될 일이 없으며, push 메서드에는 E 타입 원소만 들어오는 것이 보장되기 때문에, 아이템 27에서 다루었던 @SuppressWarnings 어노테이션을 통해 해당 경고를 제거했습니다.
코드에서 볼 수 있듯이, 첫 번째 방법을 적용하면 형변환을 배열을 생성할 때 단 한 번만 진행하며 가독성이 좋은 것을 확인할 수 있습니다.
다만 해당 방법을 적용했을 때 단점은 힙 오염이 발생할 수 있다는 점입니다.
- 런타임에서는 배열의 실제 타입이 E[]가 아닌 Object[]이기 때문에 힙 오염이 발생할 수 있음
- push() 메서드에 E 타입만 들어오는 것을 보장하기 때문에 Object 배열을 생성한 후 형변환을 통해 E 타입의 배열로 캐스팅하고 있지만
- elements 배열은 E 타입의 배열로 선언되었지만, 실제로는 Object 타입의 배열로 생성
- heapPollutionExample 메서드에서는 배열을 Object[] 타입으로 참조하고 있으며, 이 배열에는 Integer 타입의 객체를 추가
- 이는 E 타입의 배열로 간주되는 elements에 Integer 객체가 들어가게 되면서 힙 오염이 발생
방법 2
두 번째 방법은 첫 번째 방법과 같이 Object 배열을 사용하되 생성자에서 배열을 생성할 때 형변환을 하는 대신 배열이 반환하는 원소를 E로 형변환하는 방식입니다.
두 번째 방법을 적용하면 Stack 코드가 다음과 같이 바뀝니다.
해당 방법 또한 첫 번째 방법과 유사하게, push() 메서드에서는 오직 E 타입만을 허용하기 때문에 pop() 메서드 내에서의 형변환이 안전함이 보장됩니다. 더불어, @SuppressWarnings 어노테이션을 활용하여 비검사 경고를 제거했습니다.
해당 방식을 적용할 경우 첫 번째 방법의 단점이었던 힙 오염은 발생하지 않지만 배열에서 원소를 읽을 때마다 형변환을 해줘야 하는 단점이 존재합니다.
방법 1 vs 방법 2
두 방법의 장단점을 정리하면 아래와 같습니다.
방법 1 장점
- 배열 생성 시 딱 한 번만 형변환을 수행
- 가독성이 좋음
방법 1 단점
- 힙 오염 발생 가능성 존재
방법 2 장점
- 힙 오염 발생 가능성 원천 차단
방법 2 단점
- 배열에서 원소를 읽을 때마다 형변환 수행
두 방법 모두 일정한 지지를 받고 있지만, 첫 번째 방법이 가독성이 더 좋으며 형변환을 생성자에서만 수행하기 때문에 첫 번째 방법이 더 많이 선택되고 있습니다.
그러나 힙 오염에 대한 우려가 있다면 두 번째 방법을 계속해서 사용하는 것도 올바른 선택일 수 있습니다.
비고
지금까지 설명한 스택 예시 코드는 아이템 28에서 다루었던 "배열보다는 리스트를 우선하라"와 모순되게 List 대신 Object 배열을 사용합니다.이는 제네릭 타입 내 리스트를 사용하는 것이 항상 가능하지도 않을뿐더러 반드시 좋은 것이 아니기 때문입니다.자바에서 List를 기본 타입으로 제공하지 않기 때문에 ArrayList 같은 제네릭 타입도 결국에는 내부적으로 기본 타입인 배열을 사용해 구현해야 하고 HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 합니다.
참고
이펙티브 자바
이펙티브 자바 완벽 공략 2부 - 백기선 강사님
'JAVA > Effective Java' 카테고리의 다른 글
[아이템 31] 한정적 와일드카드를 사용해 API 유연성을 높이라 (2) | 2024.02.21 |
---|---|
[아이템 30] 이왕이면 제네릭 메서드로 만들라 (0) | 2024.02.20 |
[아이템 28] 배열보다는 리스트를 사용하라 (1) | 2024.02.18 |
[아이템 27] 비검사 경고를 제거하라 (1) | 2024.02.16 |
[아이템 26] 로 타입은 사용하지 말라 (0) | 2024.02.16 |