From 211aeda5ff7dd5f6f8c3b5d5a33277de7671cca3 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Fri, 11 Apr 2025 23:36:15 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=2033:=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=95=88=EC=A0=84=20=EC=9D=B4=EC=A2=85=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=EB=A5=BC=20=EA=B3=A0=EB=A0=A4?= =?UTF-8?q?=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...40\353\240\244\355\225\230\353\235\274.md" | 152 ++++++++++++++++++ README.md | 2 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 "05\354\236\245/\354\225\204\354\235\264\355\205\234 33 \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" diff --git "a/05\354\236\245/\354\225\204\354\235\264\355\205\234 33 \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" "b/05\354\236\245/\354\225\204\354\235\264\355\205\234 33 \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" new file mode 100644 index 0000000..a44f49d --- /dev/null +++ "b/05\354\236\245/\354\225\204\354\235\264\355\205\234 33 \355\203\200\354\236\205 \354\225\210\354\240\204 \354\235\264\354\242\205 \354\273\250\355\205\214\354\235\264\353\204\210\353\245\274 \352\263\240\353\240\244\355\225\230\353\235\274.md" @@ -0,0 +1,152 @@ +# 아이템 33. 타입 안전 이종 컨테이너를 고려하라 + +## 개요 + +제네릭은 `List`, `Map`와 같이 컨테이너 자체에 타입 매개변수가 적용된다. + +> 따라서 한 컨테이너는 보통 타입의 수가 제한된다. +> + +하지만, 우리는 다양한 타입의 객체를 저장하면서도 타입 안전성을 유지해야 할 때가 있다. + +~~그런가?~~ + +다행히 해법은 있다. + +바로 **타입 안전 이종 컨테이터 패턴(type safe heterogeneous container pattern)**이다. + + + +설명을 읽어도 이해가 잘 되지 않으니 코드로 살펴보자. + +## 예제 + +우리는 타입 별로 즐겨찾는 인스턴스를 저장하고 검색할 수 있는 `Favorates` 클래스를 만들 것이다. + +> String → “문자”, Integer → 123 +> + +구현하는 방법을 알아보자. + +### 1. 각 타입의 `Class` 객체를 매개변수화한 키 역할로 사용하자. + +- `class`의 리터럴 타입이 `Class`가 아닌 `Class`이기 때문에 가능하다. + +> `String.class` → `Class`, `Integer.class` → `Class` +> +- 이때 컴파일타임 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고 받는 class 리터럴을 **타입 토큰(type token)**이라 한다. + +```java +// 타입 안전 이종 컨테이너 API +public class Favorites { + public void putFavorite(Class type, T instance); + public T getFavorite(Class type); +} +``` + +```java +// 사용 예시 +public static void main(String[] args) { + Favorites f = new Favorites(); + + f.putFavorite(String.class, "Java"); + f.putFavorite(Integer.class, 0xcafebabe); + f.putFavorite(Class.class, Favorites.class); + + String favoriteString = f.getFavorite(String.class); + int favoriteInteger = f.getFavorite(Integer.class); + Class favoriteClass = f.getFavorite(Class.class); + + System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName()); +} +``` + +### 2. 값을 넣을 때와 뺄 때 해당 키의 타입을 함께 확인 + +```java +// 타입 안전 이종 컨테이너 구현 +public class Favorites { + private Map, Object> favorites = new HashMap<>(); + + public void putFavorite(Class type, T instance) { + favorites.put(Objects.requireNonNull(type), type.cast(instance)); + } + + public T getFavorite(Class type) { + return type.cast(favorites.get(type)); + } +} +``` + +구현한 코드를 하나하나 살펴보자. + +**먼저, `Map, Object>` 를 보면** + +- 키: 와일드카드를 써서 `put`이 불가능할 것 같지만, **맵이 아니라 키가 와일드카드 타입**이므로 `Class, Class`식으로 될 수 있다. +- 값: 단순히 `Object`를 씀으로써 모든 값이 키로 명시한 타입임을 보증하지 않는다. + +→ 형변환을 명시적으로 할 필요가 없다. + +**다음으로 `putFavorite()`를 보면** + +- `Class`의 `cast()`를 통해 동적 형변환을 사용하여 런타임 타입 안전성을 확보했다. + +**마지막으로 `getFavorite()`는** + +- 주어진 `Class` 객체에 해당하는 값을 `favorites` 맵에서 꺼내고, +- `Class`의 `cast()` 메서드를 사용해 객체 참조를 `Class` 객체가 가리키는 타입으로 동적 형변환한다. + + + +> 위처럼 cast를 사용해 런타임 타입 안전성을 확보한 것으로 `java.util.Collections`에 `checkedSet, checkedList, checkedMap`과 같은 메서드가 있다. +> + +## 제약사항 + +`List`과 같은 실체화 불가 타입은 사용 불가능하다. + +> 즉, `String`이나 `String[]`은 저장할 수 있어도 `List`은 저장할 수 없다. +> + +> 이는 `List`의 리터럴이 `List.class`를 공유하기 때문이다! +> + +## 해결책 - 슈퍼 타입 토큰 + +옮긴이에 따르면 제네릭 타입도 다룰 수 있게 하는 **슈퍼 타입 토큰**이라는 게 있다. + +```java +// 슈퍼 타입 토큰 예시 +Type listOfStringType = new TypeRef>() {}.getType(); +``` + +글이 길어지므로 궁금하다면 따로 알아보도록 하자. + +## 결론 + +- 단일 원소 타입만을 지원하는 제네릭의 한계를 넘어서는 방법이다. +- 타입 토큰(Class)을 키로 사용하여 타입 안전성을 보장한다. +- API가 다양한 타입을 지원해야 할 때 사용하면 좋다. +- 실무에서는 데이터베이스 행, 애너테이션, XML 문서 등을 사용할 때 이 패턴이 활용된다. \ No newline at end of file diff --git a/README.md b/README.md index b66ba50..ea0f905 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ - [x] 30. 이왕이면 제네릭 메서드로 만들라 (태태) - [x] 31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (모리) - [x] 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (태태) -- [ ] 33. 타입 안전 이종 컨테이너를 고려하라 (모리) +- [x] 33. 타입 안전 이종 컨테이너를 고려하라 (모리) - [ ] 34. int 상수 대신 열거 타입을 사용하라 (태태) - [ ] 35. ordinal 메서드 대신 인스턴스 필드를 사용하라 (모리) - [ ] 36. 비트 필드 대신 EnumSet을 사용하라 (태태)