Java generics - Map of (typed) maps

I am working on a (simple) caching solution where a service can request a Cache object from the Cache Map. A Cache object works in much the same way as a Map object, with a key and value and methods for accessing and storing objects.

I came up with the following solution, but as you can see, it contains a cast (because get () cannot know what types of nested object should be).

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

Now, my questions are:

  • Is this a “safe” approach as long as cool views are handled properly? (probably catch them and pack them in a special exception class)
  • Is there a "safe" alternative? One with generics, if at all possible, because I like them and don't like ghosts.
  • (not directly related) Is it thread safe? I guess not, but then I'm not a slicer. Is it enough to just make the whole method synchronized, or will it (with half a dozen clients) cause too much overhead / locks? Is there a neat solution for this?

Edit: Woo, many answers, thanks! Editing here to describe the strangeness I found by actually testing this:

  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. 
+4
source share
5 answers

You can create a composite key that consists of your current identifier and two instances of the class (one for the key, one for the value)

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

To make this thread safe, use ConcurrentHashMap instead of putIfAbsent (..)

+1
source

On the issue of thread safety, no, this is not a safe thread. You should look at ConcurrentHashMap or Google Guava MapMaker

+1
source

You can simply synchronize the whole method to make it thread safe. Provided that it is not called often, it will be quite effective. If you want to make the code more secure, I suggest you try the following: add runtime checking for types.

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

Is this a “safe” approach for how long are classcastExceptions handled properly? (probably catch those and pack them into the custom exception class)

In fact, the safest way to handle this would be to create a cache key using key types and values. Something like that:

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

Thus, you will be sure that the cache has the specified type.

Is there a "safe" alternative? One with generics, if at all possible, because I like them and don’t like casts.

Basically: if you do not like uncontrolled casting, you will have to provide implementation types to all methods.

(not directly connected) thread safe? I guess not, but then, I'm not a slicer. Is it enough to just make the whole method synchronized or will (with half a dozen clients) cause too much overhead / blocking? Is there a neat solution for this?

Method synchronization is terrible. Use ConcurrentHashMap and putIfAbsent() method

0
source

Yesterday was a question . Your decision is unsafe, because nothing is said about the key, which implies the type of value. In another question, the key was Callable<T> , and the value was T Thus, a custom card can be created that would insure the type of security and prevent damage to the base card.

0
source

Source: https://habr.com/ru/post/1336107/


All Articles