2011-01-19 3 views
4

Я работаю над (простым) кешированием, которое может запрашивать объект Cache из карты кешей. Объект Cache работает практически так же, как и Map, с ключом и значением и методами доступа и хранения объектов.Java generics - Карта (типизированных) карт

Я придумал следующее решение, но, как вы можете видеть, он содержит приведение (потому что 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

+0

После попытки: мне очень нравится ваше решение. При попытке получить объект кэша с другим типом возвращается новый объект кеша. Элегантный. Объект идентификатора кеша также может быть постоянно сохранен в клиентах, так как неизменяемая косая черта не изменяется. – fwielstra

+0

Рассмотрите возможность использования com.google.common.collect.MapMaker.makeComputingMap для поточно-безопасного решения. Замените ArrayList собственным классом, если вы хотите сохранить память и/или время (не забудьте реализовать hashCode и равно). – maaartinus

0

Вы можете просто сделать весь метод synchornized, чтобы сделать его поточно. При условии, что он не называется часто, он будет достаточно эффективным. Если вы хотите сделать код более безопасным, я предлагаю вам попробовать следующее: добавьте проверку времени выполнения для типов.

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 или Google гуавы-х MapMaker

0

Является ли это «безопасный» подход, до тех пор, как classcastexceptions обрабатываются правильно? (Вероятно, чтобы поймать эти и упаковать их в класс исключений пользовательского )

На самом деле, самый безопасный способ справиться, что бы создать ключ кэша с помощью ключа и значения типов. Что-то вроде этого:

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

Таким образом, вы уверены, что кеш имеет указанный тип.

Есть ли «безопасная» альтернатива? Один с дженериками, если это вообще возможно, , потому что мне они нравятся и не нравятся отливки.

В принципе: если вам не нравится неконтролируемое литье, вам придется поставлять типы реализации ко всем методам.

(не связано напрямую) Это threadsafe? Я предполагаю, что нет, но тогда, Я не специалист по нарезке. Достаточно ли , чтобы сделать весь метод синхронизированным или будет (с полудюжинами клиентов) вызывать слишком много накладные расходы/блокировка? Есть ли для этого решение ?

Синхронизация метода ужасна. Используйте ConcurrentHashMap и это putIfAbsent() метод

0

Вчера был аналогичный question. Ваше решение небезопасно, так как ничего не говорится о ключе, которое подразумевает тип значения. В другом вопросе ключ был Callable<T>, и значение было T. Таким образом, может быть создана пользовательская карта, в которой застрахован тип безопасности и предотвращается повреждение базовой карты.

Смежные вопросы