首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 泛型實現類型安全異構容器:從 Map<String,Object> 到 Type Token 模式

     編輯:Java 相關知識

問題背景:Map 的類型擦除痛點

普通 Map<String, Object> 存取值時需強制轉型,且無法編譯期檢查類型匹配:


Map<String, Object> map = new HashMap<>();
map.put("key", runnable);
Executor executor = (Executor) map.get("key"); // 運行時 ClassCastException

Joshua Bloch 在《Effective Java》第 29 條提出 類型安全異構容器 模式,利用泛型將類型信息綁定到 Key 上,實現編譯期類型安全。

核心思想:Type Token(類型令牌)

Class<T> 是泛型化的,可作為攜帶類型信息的 Key:


public class Context {
    private final Map<Class<?>, Object> values = new HashMap<>();

    public <T> void put(Class<T> key, T value) {
        values.put(key, value);
    }

    public <T> T get(Class<T> key) {
        return key.cast(values.get(key)); // 動態轉型,類型安全
    }
}

使用示例


Context ctx = new Context();
ctx.put(Runnable.class, () -> System.out.println("run"));
ctx.put(Executor.class, Runnable::run);

Runnable r = ctx.get(Runnable.class);     // 編譯期類型安全
Executor e = ctx.get(Executor.class);     // 不會混淆
// Runnable r2 = ctx.get(Executor.class); // 編譯報錯!

進階:支持同類型多實例(自定義 Key)

若需存儲多個 Runnable,引入帶標識符的 Key:


public class Key<T> {
    private final String identifier;
    private final Class<T> type;

    public Key(String identifier, Class<T> type) {
        this.identifier = identifier;
        this.type = type;
    }

    // 必須重寫 equals/hashCode,否則 Map 查找失效
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
}

public class Context {
    private final Map<Key<?>, Object> values = new HashMap<>();

    public <T> void put(Key<T> key, T value) {
        values.put(key, value);
    }

    public <T> T get(Key<T> key) {
        return key.type.cast(values.get(key));
    }
}

工廠方法簡化創建


public static <T> Key<T> key(String id, Class<T> type) {
    return new Key<>(id, type);
}

// 使用
Context ctx = new Context();
ctx.put(key("task1", Runnable.class), () -> System.out.println("task1"));
ctx.put(key("task2", Runnable.class), () -> System.out.println("task2"));

Runnable t1 = ctx.get(key("task1", Runnable.class)); // 精確獲取

兩大局限性(Effective Java 指出)

  1. 惡意客戶端可用 Raw Type 破壞安全:ctx.put((Class) Runnable.class, "not a Runnable")。
    防禦:put 方法內部做 key.cast(value) 防禦性檢查。
  2. 不可用於非具體化類型:如 List<String>、Map<Integer, String> 等泛型類型。
    原因:類型擦除導致它們共享同一 Class 對象(List.class)。
    現狀:無完美解決方案,可用 TypeReference(Gson/Jackson 風格)緩解。

實戰應用場景

  • Spring BeanFactory 獲取 Bean:getBean(Class<T> requiredType)
  • 緩存框架:cache.get(key, User.class)
  • 依賴注入容器:按類型查找組件
  • 插件系統:註冊/獲取不同類型的擴展點

總結

類型安全異構容器模式將「類型參數從容器移至鍵上」,巧妙繞過 Java 泛型擦除限制。核心技巧:

  1. 用 Class<T> 或自定義 Key<T> 攜帶類型信息
  2. 利用 Class.cast() 替代強制轉型,實現動態類型檢查
  3. 注意 Raw Type 攻擊與非具體化類型限制

這是 Java 泛型進階應用的經典案例,深刻理解後能設計出更優雅、類型安全的 API。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved