Java generics - Карта (типизированных) карт - PullRequest
4 голосов
/ 19 января 2011

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

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

Теперь мои вопросы:

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

Edit: 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.

Ответы [ 5 ]

1 голос
/ 19 января 2011

Вы можете создать составной ключ, который состоит из вашего текущего идентификатора и двух экземпляров класса (один для ключа, один для значения)

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 (..)

1 голос
/ 19 января 2011

На вопрос безопасности потока, нет, это не потокобезопасно.Вы должны посмотреть на ConcurrentHashMap или Google Guava's MapMaker

0 голосов
/ 19 января 2011

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

0 голосов
/ 19 января 2011

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

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

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

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

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

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

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

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

0 голосов
/ 19 января 2011

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

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;
}
...