2011-01-19 170 views
4

我正在研究一個(簡單)緩存解決方案,其中一個服務可以從一個緩存映射請求緩存對象。一個Cache對象的工作原理與Map一樣,具有一個鍵和一個值以及訪問和存儲對象的方法。Java泛型 - 映射(鍵入)映射

我想出了以下解決方案,但正如您所看到的,它包含一個轉換(因爲get()無法知道嵌套對象的類型應該是什麼)。

private final Map<String, Cache<?, ?>> caches = new HashMap<String, Cache<?, ?>>(); 

public <K, V> Cache<K, V> getOrCreateCache(String identifier) { 
    if (caches.containsKey(identifier)) { 
     return (Cache<K, V>) caches.get(identifier); 
    } else { 
     Cache<K, V> cache = new CacheImpl<K, V>(); 
     caches.put(identifier, cache); 
     return cache; 
    } 
} 

private void test() { 
    Cache<String, String> strCache = getOrCreateCache("string cache"); 
    strCache.set("key", "value"); 
} 

現在,我的問題:

  • 這是一個 '安全' 的做法,只要ClassCastExceptions異常得到妥善處理? (可能會抓住那些並將它們打包成自定義異常類)
  • 是否存在「安全」選擇?一個泛型,如果可能的話,因爲我喜歡他們,不喜歡演員。
  • (並不直接相關)這是線程安全嗎?我不假設,但是,我不是線程專家。僅僅使整個方法同步就足夠了,還是會(有六個客戶端)造成太多開銷/鎖定?有沒有一個很好的解決方案?

編輯:Woo,很多答案,謝謝!在這裏編輯來描述我在實際測試時發現的一個奇怪現象:

Cache<String, String> someCache = service.getOrCreateCache(cacheIdentifier); 
    someCache.set("asdf", "sdfa"); 
    Cache<String, Integer> someCacheRetrievedAgain = service.getOrCreateCache(cacheIdentifier); 
    System.out.println(someCacheRetrievedAgain.get("asdf")); // prints "sdfa". No errors whatsoever. Odd. 

回答

1

您可以創建一個由當前的標識符和(爲值的一個關鍵,一個)類的兩個實例的複合鍵

public <K, V> Cache<K, V> getOrCreateCache(String identifier, Class<K> keyClass, Class<V> valueClass) { 
    Identifier cacheIdentifier = new Identifier(identifier, keyClass, valueClass); 
    // safe cast as we know that this cacheIdentifier must has a Cache<K, V> 
    Cache<K, V> cache = (Cache<K, V>) caches.get(identifier); 
    if (cache == null) { 
    cache = new CacheImpl<K, V>(); 
    caches.put(cacheIdentifier, cache); 
    } 
    return cache; 
} 

/* 
* not the most efficient implementation, but correctly implements hashCode and equals 
* which is all we need 
*/ 
private static class CacheIdentifier extends ArrayList<Object> { 
    private CacheIdentifier(String identifier, Class<K> keyClass, Class<V> valueClass) { 
    super(3); 
    // TODO check for null 
    add(identifier); 
    add(keyClass); 
    add(valueClass); 
    } 
} 

爲了使這個線程安全的,使用ConcurrentHashMap代替以及putIfAbsent(..)

+0

我喜歡ArrayList的擴展來快速組成一個排序的標識符,很好的技巧,應該記住它。我會嘗試你的解決方案。 – fwielstra 2011-01-19 09:32:06

0

您可以使整個方法同步化以使其線程安全。只要不經常調用它就足夠有效了。如果你想讓代碼更安全,我建議你嘗試下面的代碼,爲這些類型添加一個運行時檢查。

public <K, V> Cache<K, V> getOrCreateCache(String identifier, Class<K> kClass, Class<V> vClass) { 
    Cache<K, V> cache = (Cache<K, V>) caches.get(identifier); 
    if(cache == null) 
     caches.put(identifier, cache = new CacheImpl<K, V>(kClass, vClass)); 
    assert cache.kClass() == kClass; 
    assert cache.vClass() == vClass; 
    return cache; 
} 
1

關於線程安全性問題,不,它不是線程安全的。你應該看看ConcurrentHashMap或谷歌番石榴的MapMaker

0

這是一個「安全」的做法,只要 作爲ClassCastExceptions異常的處理 正常嗎? (可能會趕上 那些和他們打包成一個自定義 異常類)

其實,來處理這將是創建使用鍵和值類型緩存鍵最安全的方式。類似這樣的:

public String getCacheKey(Class<?> keyType, Class<?> valueType, String uniqueId){ 
    return keyType.getName()+"-"+valueType.getName()+"-"+uniqueId; 
} 

這樣你就可以確定緩存是指定的類型。

有沒有'安全'的選擇?一個 與泛型,如果可能的話, ,因爲我喜歡他們並且不喜歡 強制轉換。

基本上:如果你不喜歡未經檢查的鑄造,你將不得不爲所有方法提供實現類型。

(與此無關)是否爲 線程安全?我不假設,但是, 我不是線程專家。是否 足以使整個方法 同步,或者會(與 六個客戶端)導致太多 開銷/鎖定?有沒有一個整潔的 解決方案?

同步該方法是可怕的。使用ConcurrentHashMap並且它的putIfAbsent()方法

0

昨天有類似的question。你的解決方案是不安全的,因爲沒有任何關於暗示值的類型的鍵。其他問題的關鍵是Callable<T>和價值是T。因此可以製作保證類型安全的自定義地圖,並防止底層地圖被破壞。