제네릭은 Set<E>, ThreadLocal<T> 등의 단일 원소 컨테이너에도 흔히 사용되는데, 문제될건 없지만 더 유연한 수단이 필요할 때도 종종 있다. 예를 들면 데이터베이스의 행은 임의 개수의 열을 가질 수 있는데, 모든 열을 타입 안전하게 이용할 수 있으면 so cool
이렇게 하기 위한 방법은 컨테이너 대신 키를 매개변수화한 다음에 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공하면된다. 이렇게 하면 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장해준다. 이러한 설계 방식을 타입 안전 이종 컨테이너 패턴이라 한다.
타입 안전 이종 컨테이너❓❓❓
말이 어려운데 쉽게 말하면
여러 다른 종류들로 이루어진 값을 저장하는 타입 안전한 객체라고 생각하면된다.
🍑 본론
example
타입 안전 이종 컨테이너 ❌
// 데이터베이스 컬럼을 표현하는 클래스
class Column {
private String name;
public Column(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 데이터베이스 행을 표현하는 클래스
class DatabaseRow {
// 컬럼과 값을 매핑하는 Map
private Map<Column, Object> data = new HashMap<>();
// 컬럼과 값을 매핑하여 저장
public void put(Column column, Object value) {
data.put(column, value);
}
public Object get(Column column) {
return data.get(column);
}
}
public class Main {
public static void main(String[] args) {
// 데이터베이스 행 생성
DatabaseRow row = new DatabaseRow();
// 데이터베이스 컬럼 객체 생성
Column ageColumn = new Column("age");
Column nameColumn = new Column("name");
// 데이터베이스 행에 컬럼과 값을 매핑하여 저장
row.put(ageColumn, 30);
row.put(nameColumn, "John Doe");
// 데이터베이스 행에서 컬럼 값을 가져와 출력
// 이 부분에서는 컴파일러가 타입을 확인할 수 없기 때문에 형변환
int age = (int) row.get(ageColumn);
String name = (String) row.get(nameColumn);
// 출력
System.out.println("Age: " + age);
System.out.println("Name: " + name.toUpperCase());
// 이 부분에서 런타임 에러가 발생할 수 있음
// 예를 들면 이런 식으로 잘못된 타입으로 형변환하여 런타임 에러를 발생시킴
String age2 = (String) row.get(ageColumn); // 잘못된 형변환으로 런타임 에러 발생
System.out.println("Age: " + age2.toUpperCase());
}
}
타입 안전 이종 컨테이너 ⭕
// 제네릭 클래스를 사용하여 컨테이너를 생성
class Container<K, V> {
private Map<K, V> map = new HashMap<>();
// 키와 값을 매핑하여 저장하는 메서드
public void put(K key, V value) {
map.put(key, value);
}
// 키를 사용하여 값을 가져오는 메서드
public V get(K key) {
return map.get(key);
}
}
//데이터베이스 행을 표현하는 클래스
class DatabaseRow {
// 데이터베이스 컬럼을 키로 사용하는 타입 안전 이종 컨테이너
private Container<Column<?>, Object> data = new Container<>();
// 데이터베이스 컬럼과 값을 매핑하여 저장하는 메서드
public <T> void putColumn(Column<T> column, T value) {
data.put(column, value);
}
// 데이터베이스 컬럼을 키로 사용하여 해당 컬럼의 값을 가져오는 메서드
public <T> T getColumnValue(Column<T> column) {
// 컨테이너에서 해당 컬럼을 키로 사용하여 값을 가져옴
return (T) data.get(column);
}
}
// 데이터베이스 컬럼을 표현하는 제네릭 클래스
class Column<T> {
private String name;
public Column(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
// 데이터베이스 행 생성
DatabaseRow row = new DatabaseRow();
// 데이터베이스 컬럼 객체 생성
Column<Integer> ageColumn = new Column<>("age");
Column<String> nameColumn = new Column<>("name");
// 데이터베이스 행에 컬럼과 값을 매핑하여 저장
row.putColumn(ageColumn, 30);
row.putColumn(nameColumn, "John Doe");
// 데이터베이스 행에서 컬럼 값을 가져와 출력
System.out.println("Age: " + row.getColumnValue(ageColumn));
System.out.println("Name: " + row.getColumnValue(nameColumn).toUpperCase());
// 잘못된 형변환으로 컴파일 에러 발생
// 런타임 이전에 에러를 잡을 수 있음
String age = (String) row.getColumnValue(ageColumn);
System.out.println("Age: " + age.toUpperCase());
}
}
🍑 결론
컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다. 타입 안전 이종 컨테이너는 Class를 키로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토큰이라 한다. 또한, 직접 구현한 키 타입도 쓸 수 있다. 예컨대, 데이터베이스의 행(컨테이너)을 표현한 DatabaseRow 타입에는 제네릭 타입인 Column<T>를 키로 사용할 수 있다.
📍 참고
Favorites 클래스
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
putFavorite : Class 객체와 인스턴스를 favorites에 추가
getFavorite : Class 객체에 해당하는 값을 favorites에서 꺼낸다.
이때 Class의 cast() 메서드를 사용해서 객체가 가리키는 타입이 T 타입의 인스턴스가 맞는지 검사하고 리턴한다
(아닐 경우 ClassCastExceptin을 던진다 )
참고: cast 메서드
public final class Class<T> {
...
@SuppressWarnings("unchecked")
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
....
}
main 메서드
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());
}
Chapter : 5. 제네릭
Item 33. 타입 안전 이종 컨테이너를 고려하라
Assignee : hyunsoo10
🍑 서론
제네릭은
Set<E>
,ThreadLocal<T>
등의 단일 원소 컨테이너에도 흔히 사용되는데, 문제될건 없지만 더 유연한 수단이 필요할 때도 종종 있다. 예를 들면 데이터베이스의 행은 임의 개수의 열을 가질 수 있는데, 모든 열을 타입 안전하게 이용할 수 있으면 so cool이렇게 하기 위한 방법은 컨테이너 대신 키를 매개변수화한 다음에 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공하면된다. 이렇게 하면 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장해준다. 이러한 설계 방식을
타입 안전 이종 컨테이너 패턴
이라 한다.타입 안전 이종 컨테이너❓❓❓ 말이 어려운데 쉽게 말하면
여러 다른 종류들로 이루어진 값을 저장하는 타입 안전한 객체
라고 생각하면된다.🍑 본론
example
타입 안전 이종 컨테이너 ❌
타입 안전 이종 컨테이너 ⭕
🍑 결론
컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다. 타입 안전 이종 컨테이너는 Class를 키로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토큰이라 한다. 또한, 직접 구현한 키 타입도 쓸 수 있다. 예컨대, 데이터베이스의 행(컨테이너)을 표현한 DatabaseRow 타입에는 제네릭 타입인
Column<T>
를 키로 사용할 수 있다.📍 참고
Favorites 클래스
참고: cast 메서드
main 메서드
Referenced by