Item 33. Type Safe Heterogeneous Container

νƒ€μž… μ•ˆμ „ 이쒅 μ»¨ν…Œμ΄λ„ˆλ₯Ό κ³ λ €ν•˜λΌ

μ œλ„€λ¦­μ€ Set, Map 같은 μ»¬λ ‰μ…˜κ³Ό ThreadLocal, AtomicReference 같은 단일 μ›μ†Œ μ»¨ν…Œμ΄λ„ˆμ—λ„ 자주 μ‚¬μš©λœλ‹€. 보톡은 ν•˜λ‚˜(Set) ν˜Ήμ€ 두 개(Map)의 νƒ€μž… λ§€κ°œλ³€μˆ˜κ°€ μ‚¬μš©λ˜μ§€λ§Œ, λ•Œλ‘œλŠ” μ„Έ 개 μ΄μƒμ˜ μž„μ˜ κ°œμˆ˜κ°€ ν•„μš”ν•œ κ²½μš°κ°€ μžˆλ‹€.

νƒ€μž… μ•ˆμ „ 이쒅 μ»¨ν…Œμ΄λ„ˆ(type safe heterogeneous container)

νƒ€μž… μ•ˆμ „ 이쒅 μ»¨ν…Œμ΄λ„ˆ νŒ¨ν„΄μ„ μ΄μš©ν•˜μ—¬ μž„μ˜ νƒ€μž…μ˜ μ›μ†Œλ₯Ό μ €μž₯ν•˜κ³  검색할 수 μžˆλŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό λ§Œλ“€ 수 μžˆλ‹€. μ»¨ν…Œμ΄λ„ˆ λŒ€μ‹  ν‚€λ₯Ό λ§€κ°œλ³€μˆ˜ν™”ν•œ λ’€, μ»¨ν…Œμ΄λ„ˆμ— 값을 λ„£κ±°λ‚˜ λΊ„ λ•Œ λ§€κ°œλ³€μˆ˜ν™”ν•œ ν‚€λ₯Ό ν•¨κ»˜ μ œκ³΅ν•˜λŠ” λ°©μ‹μœΌλ‘œ μ΄λ ‡κ²Œ ν•˜λ©΄ μ œλ„€λ¦­ νƒ€μž… μ‹œμŠ€ν…œμ΄ κ°’μ˜ νƒ€μž…μ΄ 킀와 κ°™μŒμ„ 보μž₯ν•˜κ²Œ λœλ‹€.

각 νƒ€μž…μ˜ Class 객체λ₯Ό λ§€κ°œλ³€μˆ˜ν™”ν•œ ν‚€ μ—­ν• λ‘œ μ‚¬μš©ν•  수 μžˆλŠ”λ°, 이 방식이 λ™μž‘ν•˜λŠ” μ΄μœ λŠ” class의 ν΄λž˜μŠ€κ°€ μ œλ„€λ¦­μ΄κΈ° λ•Œλ¬Έμ΄λ‹€. μ˜ˆμ‹œλ‘œ μ•„λž˜μ˜ FavoritesλΌλŠ” 클래슀λ₯Ό 보자.

class Favorites {

    private final Map<Class<?>, Object> favorites = new HashMap<>();

    // 클래슀의 λ¦¬ν„°λŸ΄ νƒ€μž…μ€ Classκ°€ μ•„λ‹Œ Class<T>둜 ν‘œν˜„ν•  수 μžˆλ‹€.(= νƒ€μž… 토큰)
    public <T> void putFavorite(Class<T> type, T instance) {
        // Class 객체와 μΈμŠ€ν„΄μŠ€λ₯Ό λ§€ν•‘ν•˜μ—¬ μ €μž₯, 킀와 κ°’ μ‚¬μ΄μ˜ νƒ€μž… 관계가 μ†Œλ©Έλ˜μ§€λ§Œ λ…Όλ¦¬μ μœΌλ‘œλŠ” 보μž₯λœλ‹€.
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        // favorites.get(type)둜 Class 객체λ₯Ό 톡해 μΈμŠ€ν„΄μŠ€λ₯Ό κ°€μ Έμ˜¨ λ’€, νƒ€μž… μΊμŠ€νŒ…ν•˜μ—¬ λ°˜ν™˜
        // * cast λ©”μ„œλ“œ: ν˜•λ³€ν™˜ μ—°μ‚°μžμ˜ 동적 λ²„μ „μœΌλ‘œ, νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€μΈμ§€ ν™•μΈν•œ λ’€ μΈμŠ€ν„΄μŠ€λ₯Ό T νƒ€μž…μœΌλ‘œ λ°˜ν™˜(μ‹€νŒ¨ μ‹œ ClassCastException λ°œμƒ)
        return type.cast(favorites.get(type));
    }
}

class Main {

    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite(String.class, "Java"); // String 클래슀 νƒ€μž… -> Class<String>
        f.putFavorite(Integer.class, 0xeee); // Integer 클래슀 νƒ€μž… -> Class<Integer>
        f.putFavorite(Class.class, Favorites.class); // Class 클래슀 νƒ€μž… -> Class<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", favoriteString, favoriteInteger, favoriteClass.getName());
    }
}

Favoriteμ—μ„œ Map<Class<?>, Object>λ₯Ό μ‚¬μš©ν•˜κ³  있으며, κ·Έ νŠΉμ§•μ€ λ‹€μŒκ³Ό κ°™λ‹€.

  • Key(Class<?>): 클래슀 λ¦¬ν„°λŸ΄λ‘œ, νŠΉμ • 클래슀둜 μ œν•œν•˜μ§€ μ•Šκ³ , λͺ¨λ“  클래슀λ₯Ό ν—ˆμš©ν•œλ‹€.

  • Value(Object): 클래슀의 μΈμŠ€ν„΄μŠ€μ΄λ©°, Object둜 μ œν•œν•˜μ§€ μ•Šκ³ , λͺ¨λ“  클래슀λ₯Ό ν—ˆμš©ν•œλ‹€.

  • Valueκ°€ λ‹¨μˆœ Object이기 λ•Œλ¬Έμ— 킀와 κ°’ 사이에 νƒ€μž… 관계λ₯Ό λ³΄μ¦ν•˜μ§€ μ•Šμ§€λ§Œ, λ…Όλ¦¬μ μœΌλ‘œλŠ” 킀와 κ°’μ˜ νƒ€μž… 관계가 보μž₯λœλ‹€.

?(λΉ„ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…)κ°€ μ‘΄μž¬ν•˜μ—¬ 이라 아무것도 넣을 수 μ—†λŠ” κ²ƒμ²˜λŸΌ λ³΄μ΄μ§€λ§Œ, μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ΄ 쀑첩(nested)λ˜μ–΄ 있기 λ•Œλ¬Έμ— 맡이 μ•„λ‹ˆλΌ ν‚€κ°€ μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ΄λ―€λ‘œ Class<?>λŠ” λͺ¨λ“  클래슀λ₯Ό ν—ˆμš©ν•˜λŠ” νƒ€μž…μ΄ λœλ‹€.

νƒ€μž… μ•ˆμ „ 이쒅 μ»¨ν…Œμ΄λ„ˆμ˜ μ œμ•½ 사항

νƒ€μž… μ•ˆμ „ 이쒅 μ»¨ν…Œμ΄λ„ˆ νŒ¨ν„΄μ€ μž„μ˜ νƒ€μž…μ˜ μ›μ†Œλ₯Ό μ €μž₯ν•˜κ³  검색할 수 μžˆμ§€λ§Œ, μ œμ•½ 사항이 μ‘΄μž¬ν•œλ‹€.

1. μ•…μ˜μ μΈ ν΄λΌμ΄μ–ΈνŠΈ

μ•…μ˜μ μœΌλ‘œ Class 객체λ₯Ό μ œλ„€λ¦­μ΄ μ•„λ‹Œ νƒ€μž…μœΌλ‘œ λ„˜κΈ°λ©΄ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€.(컴파일 μ‹œ 비검사 κ²½κ³  λ°œμƒ)

class Main {

    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite((Class) Integer.class, "Java"); // κ°•μ œλ‘œ Class<Integer>λ₯Ό Class둜 ν˜•λ³€ν™˜ν•˜μ—¬ λ„˜κΉ€
        int favoriteInteger = f.getFavorite(Integer.class); // ClassCastException λ°œμƒ
    }
}

String / String[] μ—λŠ” 적용 κ°€λŠ₯ν•˜λ‚˜ List<String> 같은 싀체화 λΆˆκ°€ νƒ€μž…μ€ Class 객체λ₯Ό 얻을 수 μ—†μ–΄ 문법 였λ₯˜κ°€ λ‚œλ‹€. List.classκ°€ ν—ˆμš©λœλ‹€λ©΄, List<Integer>와 List<String>이 같은 Class 객체λ₯Ό κ³΅μœ ν•˜κ²Œ λ˜μ–΄ ꡬ뢄할 수 μ—†κ²Œ 되기 λ•Œλ¬Έμ΄λ‹€. (슈퍼 νƒ€μž… ν† ν°μ΄λΌλŠ” λ°©λ²•μœΌλ‘œ 우회 κ°€λŠ₯ ν•˜λ‚˜ ν•œκ³„κ°€ μ‘΄μž¬ν•œλ‹€.)

ν•œμ •μ  νƒ€μž… 토큰(bounded type token)

μœ„μ—μ„œ κ΅¬ν˜„ν•œ Favorites ν΄λž˜μŠ€λŠ” λΉ„ν•œμ •μ  μ™€μΌλ“œμΉ΄λ“œ νƒ€μž…μ„ μ‚¬μš©ν•˜κ³  있기 λ•Œλ¬Έμ— λͺ¨λ“  클래슀λ₯Ό ν—ˆμš©ν•œλ‹€. νŠΉμ • 클래슀만 ν—ˆμš©ν•˜κ³  μ‹Άλ‹€λ©΄ ν•œμ •μ  νƒ€μž… 토큰을 μ‚¬μš©ν•˜λ©΄ λœλ‹€.(μ–΄λ…Έν…Œμ΄μ…˜ APIμ—μ„œ ν•œμ •μ  νƒ€μž… 토큰을 μ‚¬μš©ν•˜κ³  μžˆλ‹€.)

public interface AnnotatedElement {
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);
}

<T extends Annotation>둜 μ„ μ–Έν•˜μ—¬ μ–΄λ…Έν…Œμ΄μ…˜ νƒ€μž…λ§Œ ν—ˆμš©ν•˜μ—¬ μ‚¬μš©ν•˜κ³  μžˆλ‹€. 이 λ©”μ„œλ“œλ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜λ©΄ ? extends Annotation으둜 ν˜•λ³€ν™˜ν•˜κ²Œ λ˜μ–΄ 비검사 ν˜•λ³€ν™˜μ„ ν•˜κ²Œ λ˜λ―€λ‘œ 컴파일 κ²½κ³ κ°€ λ°œμƒν•œλ‹€. λ‹€λ₯Έ λ©”μ„œλ“œλ₯Ό μΆ”κ°€μ μœΌλ‘œ μ‚¬μš©ν•˜λ©΄μ„œ ν•œμ •μ  νƒ€μž… 토큰을 μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•˜λŠ” 방법은 λ‹€μŒκ³Ό κ°™λ‹€.

class Example {

    static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
        Class<?> annotationType = null; // λΉ„ ν•œμ •μ  νƒ€μž… 토큰
        try {
            annotationType = Class.forName(annotationTypeName); // 호좜된 μΈμŠ€ν„΄μŠ€ μžμ‹ μ˜ Class 객체λ₯Ό λͺ…μ‹œν•œ 클래슀둜 ν˜•λ³€ν™˜
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex); // μ˜ˆμ™Έ λ°œμƒ μ‹œ μ‹€νŒ¨
        }

        return element.getAnnotation(annotationType.asSubclass(Annotation.class));
    }
}

Last updated