【问题标题】:Java generics - Map of (typed) mapsJava 泛型 - (类型化)映射的映射
【发布时间】:2011-01-19 08:45:16
【问题描述】:

我正在研究一种(简单的)缓存解决方案,其中服务可以从缓存映射中请求缓存对象。 Cache 对象的工作原理本质上也类似于 Map,具有键和值以及访问和存储对象的方法。

我想出了以下解决方案,但正如您所见,它包含一个强制转换(因为 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");
}

现在,我的问题:

  • 只要正确处理类转换异常,这是一种“安全”的方法吗? (可能会捕获这些并将它们打包到自定义异常类中)
  • 是否有“安全”的替代方案?如果可能的话,使用泛型,因为我喜欢它们并且不喜欢演员表。
  • (不直接相关)这是线程安全的吗?我假设不是,但是,我不是线程专家。仅使整个方法同步就足够了,还是(有六个客户端)会导致过多的开销/锁定?有没有一个巧妙的解决方案?

编辑:哇,很多答案,谢谢!在这里编辑以描述我在实际测试时发现的一个奇怪之处:

    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.

【问题讨论】:

    标签: java generics caching map


    【解决方案1】:

    您可以创建一个复合键,其中包含您当前的标识符和两个 Class 实例(一个用于键,一个用于值)

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

    【讨论】:

    • 我喜欢 ArrayList 的扩展来快速组成各种标识符,好技巧,应该记住它。我会试试你的解决方案。
    • 尝试后:我非常喜欢您的解决方案。当尝试检索具有不同类型的缓存对象时,将返回一个新的缓存对象。优雅的。缓存标识符对象也可以永久存储在客户端中,因为它是不可变的斜线。
    • 考虑使用 com.google.common.collect.MapMaker.makeComputingMap 作为线程安全的解决方案。用自己的类替换 ArrayList,以防你想节省内存和/或时间(不要忘记实现 hashCode 和 equals)。
    【解决方案2】:

    关于线程安全问题,不,它不是线程安全的。你应该看看 ConcurrentHashMap 或 Google Guava 的 MapMaker

    【讨论】:

      【解决方案3】:

      您可以使整个方法同步以使其线程安全。如果它不经常被调用,它将足够有效。如果您想让代码更安全,我建议您尝试以下为类型添加运行时检查。

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

      【讨论】:

        【解决方案4】:

        这是一种“安全”的方法吗,只要 处理类转换异常 适当地? (可能会赶上 那些并将它们打包成一个自定义 异常类)

        实际上,最安全的处理方法是使用键和值类型创建缓存键。像这样的:

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

        这样你就可以确定缓存是指定类型的。

        是否有“安全”的替代方案?一 如果可能的话,使用泛型, 因为我喜欢和不喜欢 演员表。

        基本上:如果您不喜欢未经检查的强制转换,则必须为所有方法提供实现类型。

        (不直接相关)这是 线程安全?我假设不是,但是, 我不是线程专家。是吗 足以使整个方法 同步,或者会(与 六个客户)造成太多 开销/锁定?有没有整齐的 解决方案?

        同步方法很糟糕。使用ConcurrentHashMapputIfAbsent() 方法

        【讨论】:

          【解决方案5】:

          昨天有一个类似的question。您的解决方案不安全,因为密钥没有任何暗示值的类型。在其他问题中,键是Callable&lt;T&gt;,值是T。因此,可以制作自定义映射,确保类型安全,并防止底层映射被破坏。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-08-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-25
            • 1970-01-01
            • 2015-04-10
            相关资源
            最近更新 更多