Suppose I have a cache implemented as java.util.Map which stores (arbitrary) values for keys. As the values are not mandatorily present, the cache returns an java.util.Optional and is able to be provided with a java.util.function.Supplier to calculate the value for a given non-existing key.
My first naive approach was
public class Cache0 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final Optional<String> valueOptional;
if (this.mapping.containsKey(key)) {
final String value = this.mapping.get(key);
valueOptional = Optional.of(value);
} else {
valueOptional = supplier.get();
if (valueOptional.isPresent()) {
this.mapping.put(key, valueOptional.get());
}
}
return valueOptional;
}
}
but I found this very inelegant and as I learned about java.util.Map#computeIfAbsent I changed the code to the following
public class Cache1 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final String value = this.mapping.computeIfAbsent(key, absentKey -> this.getValue(supplier));
return Optional.ofNullable(value);
}
private String getValue(Supplier<Optional<String>> supplier) {
return supplier.get()
.orElse(null);
}
}
but what now bothers me is the redundant use of java.util.Optional#ofNullable in combination with the null result of the getValue method which is needed to provide java.util.Map#computeIfAbsent with the "default" value not to be inserted into the map.
In an ideal situation, something like the following would be possible
public class Cache2 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}
where java.util.Map#computeIfAbsent would skip the insertion if the second parameter represents an empty java.util.Optional and returns an java.util.Optional#empty instead but unfortunately the use of java.util.Optional#empty as "default" insert value for java.util.Map#computeIfAbsent is not supported and the code does not compile.
A further possibility would be to store a mapping of String to java.util.Optional but then the java.util.Map would store the java.util.Optional#empty as value contradicting my use-case again to be forced to store invalid mappings and removing/replacing them by hand later.
public class Cache3 {
private final Map<String, Optional<String>> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}
Is anyone aware of a better approach to handle this kind of use-case or do I have to fall back to my implementation of Cache1?
To do this kind of thing I usually use an Optional in my map - this way
map.get()!=null means I've cached the access and map.get().isPresent() tells me if a sensible value was returned.
In this case I'd use a Suplier<String> that returns null when the value is not present. Then the implementation would look like this:
public class Cache {
private final Map<String, Optional<String>> mapping = new HashMap<>();
public Optional<String> get(String key, Suplier<String> supplier) {
return mapping.computeIfAbsent(key,
unused -> Optional.ofNullable(supplier.get()) );
}
}
Absent keys do get inserted into the map, but marked as missing.
It sounds to me like you are re-inventing a Guava LoadingCache (read here about Guava Caches). While this is definitely an interesting programming exercise, the existing solution is time-proven, can be configured to your needs and works under extremely heavy load.
An example definition would be:
Cache<Key, Value> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Value>() {
#Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
This is somewhat equivalent to your usage scenario, i.e. the calculation can be different on a per-key level. Usually, you don't need this, so you'd prefer a stored calculation method inside the cache:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
Related
This is an odd question. I don't think there's a solution, but I thought I'd ask anyway.
Say I have an enum:
public enum Key {
RED(String.class),
GREEN(Integer.class),
BLUE(Short.class);
private Class<?> expectedType;
Key(Class<?> expectedType) { this.expectedType = expectedType; }
public Class<?> getExpectedType() { return expectedType; }
}
I want to use the 'expectedType' field from the Key enum as the return type of a method. See:
public class Cache {
private static Map<Key, Object> cache = new HashMap<>();
public void put(Key key, Object value) {
// Easy to validate that 'value' is of type key.getExpectedType()...
}
public <T> T get(Key key) {
Object value = cache.get(key);
// TODO need to define <T> as key.getExpectedType(). How?
}
}
See that TODO? I'd like for get() to define the return type of the 'expectedType' defined by the key parameter. E.g. if the key parameter were RED, the get() method would return a String and you could write:
String s = cache.get(Key.RED);
Is there a way to do that?
I'm thinking there isn't, but I'd love to hear of a clever solution.
Enums don't support generics, but you could use a regular class as a generic pseudo-enum:
public class Key<T> {
public static final Key<String> RED = new Key<>(String.class);
public static final Key<Integer> GREEN = new Key<>(Integer.class);
public static final Key<Short> BLUE = new Key<>(Short.class);
private final Class<T> expectedType;
private Key(Class<T> expectedType) { this.expectedType = expectedType; }
public Class<T> getExpectedType() { return expectedType; }
}
public class Cache {
private Map<Key<?>, Object> cache = new HashMap<>();
public <T> void put(Key<T> key, T value) {
cache.put(key, key.getExpectedType().cast(value));
}
public <T> T get(Key<T> key) {
return key.getExpectedType().cast(cache.get(key));
}
}
shmosel's answer is almost certainly sufficient for what you need; however, it has the slight limitation that you can't store/retrieve a generic type, because you can't get a class literal for a generic type.
Instead, you can use something like Guava's TypeCapture:
abstract class GenericKey<T> {
Type getExpectedType() {
return ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
}
which is a bit of reflective grossness that you shouldn't spend too much time looking at.
Notice that it's abstract, so you have to instantiate like:
new GenericKey<Integer>() {}
This is creating an anonymous subclass of GenericKey, which is part of the magic that makes it work with generic types.
Then, it's basically the same:
public class Cache {
private Map<GenericKey<?>, Object> cache = new HashMap<>();
public <T> void put(GenericKey<T> key, T value) {
cache.put(key.getExpectedType(), value);
}
public <T> T get(GenericKey<T> key) {
return (T) cache.get(key.getExpectedType());
}
}
Now you could have a GenericKey<List<Integer>>, using new new GenericKey<List<Integer>() {}, if you should so desire.
The downside of this approach is that you lose the ability to do checking on the value on the way in/out of the cache, so you could get heap pollution if you are careless with raw types.
I simply have a Map. But it can return a Map, which may also return a Map. It's possible up to 3 to 4 nested Maps. So when I want to access a nested value, I need to do this:
((Map)((Map)((Map)CacheMap.get("id")).get("id")).get("id")).get("id")
Is there a cleaner way to do this?
The reason I'm using a Map instead of mapping it to an object is for maintainability (e.g. when there are new fields).
Note:
Map<String, Object>
It has to be Object because it won't always return a Hashmap. It may return a String or a Long.
Further clarification:
What I'm doing is I'm calling an api which returns a json response which I save as a Map.
Here's some helper methods that may help things seem cleaner and more readable:
#SuppressWarnings("unchecked")
public static Map<String, Object> getMap(Map<String, Object> map, String key) {
return (Map<String, Object>)map.get(key);
}
#SuppressWarnings("unchecked")
public static String getString(Map<String, Object> map, String key) {
return (String)map.get(key);
}
#SuppressWarnings("unchecked")
public static Integer geInteger(Map<String, Object> map, String key) {
return (Integer)map.get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
But you would have to nest the calls:
String attrValue = getString(getMap(getMap(map, id1), id2), attrName);
Or, if you want something more funky, add the above methods as instance methods to a map impl:
public class FunkyMap extends HashMap<String, Object> {
#SuppressWarnings("unchecked")
public FunkyMap getNode(String key) {
return (FunkyMap)get(key);
}
#SuppressWarnings("unchecked")
public String getString(String key) {
return (String)get(key);
}
#SuppressWarnings("unchecked")
public Integer geInteger(String key) {
return (Integer)get(key);
}
// you can add more methods for Date, Long, and any other types you know you'll get
}
Deserialize into this class with your json library (you'll probably have to provide it with a factory method for the map class impl), then you can chain the calls more naturally:
String attrValue = map.getNode(id1).getNode(id2).getString(attrName);
The funky option is what I did for a company, and it worked a treat :)
If you don't know the depth of the JSON tree and if you worry about maintainability if new fields are added, I would recommend not to deserialize the full tree in a Map but instead use a low-level parser.
For example, if your JSON looks like the following:
{
"id": {
"id": {
"id": {
"id": 22.0
}
}
}
}
You could write something like that to get the id using Jackson:
public Object getId(String json) throws JsonParseException, IOException
{
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode id = root.get("id");
while (id != null && id.isObject())
{
id = id.get("id");
}
//Cannot find a JsonNode for the id
if (id == null)
{
return null;
}
//Convert id to either String or Long
if (id.isTextual())
return id.asText();
if (id.isNumber())
return id.asLong();
return null;
}
I would like to use Guava as cache but I can't seem to find Guava has the capability of allowing me to load multiple items and get multiple items.
I see CacheLoader has the following:
#Override
public Value load(String key) {
return getKey();
}
And what I need to load is:
#Override
public List<Value> load(List<String> keys) {
return getKeys();
}
I would also expect to get one or a list of items from the cache, but I am happy even if I had to wrap that one item into a list just to get it.
I'm new to Guava and I'm not sure if Guava has such functionality?
You can use CacheLoader.loadAll() to load multiple items, and LoadingCache.getAll() to get them.
For example:
new CacheLoader<String, Value>() {
#Override
public Value load(String key) {
return getKey();
}
#Override
public Map<String, Value> load(Iterable<? extends String> keys) {
return getKeys();
}
}
//...
List<String> keys = Arrays.asList("key1", "key2", "key3");
ImmutableMap<String, Value> values = cache.getAll(keys);
You can create a LoadingCache(just for e.g.) as:
private final LoadingCache<String, Object> cache;
where String could be your key's datatype and Object could be your value's datatype.
You can then initialise it using CacheBuilder as:
cache = CacheBuilder.newBuilder().
initialCapacity(10).
maximumSize(50).
recordStats().
build(new CacheLoader<String, Object>() {
#Override
public Object load(String s) throws Exception {
return null;
}
});
and further more implement methods to get a value from the cache based on the key and put a value into the cache for a key value pair in somewhat this format:
public Object get(String key) {
try {
return cache.getIfPresent(key);
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
public boolean put(String key, Object object) {
cache.put(key, object);
return true;
}
Public class Cache {
private Cache<Key, Value> cache;
prviate DataDAO cataDao;
public Cache(DataDAO dataDao) {
_dataDao = DataDAO;
cache = CacheBuilder.newBuilder().build();
}
public Value getValue(Key key) {
Value value;
if (cache.getIfPresent(key) == null) {
value = dataDao.getById(key);
cache.put(key, value);
return value;
}else{
return cache.getIfPresent(key);
}
}
Public List<Value> getValues(List<Key> keys) {
List<Value> values = new ArrayList<>();
List<Key> notInCacheKeys = new ArrayList<>();
for (Key key: keys) {
if (cache.getIfPresent(key)) == null) {
notInCacheKeys.add(key);
}
}
List<Value> newlyRetrievedValues = _dataDao.getByIds(notInCacheKeys);
//Store Keys and Values in order
//Return value and list of values from cache
}
}
I have decided to abandon CacheLoader and LoadingCache and just work with cache directly.
I want to make a HashMap which contains HashSets as values and returns an empty HashSet when the key is not found.
public class IsbnHashMap<K,V> extends HashMap<K,V> {
protected V defaultValue;
public IsbnHashMap(V defaultValue) {
this.defaultValue = defaultValue;
}
#Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
}
}
However my implementation does not work.
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>();
This returns "HashSet cannot be applied". If I try to change K,V in IsbnHashMap to <String, HashSet<String>> I get some funky errors as well. How can I implement this?
First it should be noted that in Java-8 you can use instead:
isbnToId.computeIfAbsent(isbn, k -> new HashSet<>()).add(_id);
Second, if you really want to do something like this in previous Java versions, you'd better to create separate method for this purpose (for example, getOrDefault()) in order not to violate the contract. Third, you need to create new HashSet<>() for every new key. If you return the same instance, it will be shared between given keys. If you don't expect users to modify it, it's better to use unmodifiable Collections.emptySet() as default value. This way users may safely do isbnToId.getOrDefault(isbn).contains(_id), but trying isbnToId.getOrDefault(isbn).add(_id) will result in exception. If you want to support the modification (prior to Java-8), you can, for example, pass the element class to the constructor instead:
public static class MyMap<K, V> extends HashMap<K, V> {
private Class<?> clazz;
public MyMap(Class<?> clazz) {
this.clazz = clazz;
}
public V getOrCompute(K key) {
V v = get(key);
if(v == null) {
try {
v = (V) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
put(key, v);
}
return v;
}
}
Usage example:
MyMap<String, Set<String>> map = new MyMap<>(HashSet.class);
map.getOrCompute("a").add("b");
map.getOrCompute("a").add("c");
map.getOrCompute("d").add("e");
System.out.println(map); // {a=[b, c], d=[e]}
Here we assume that instantiating the passed class with default constructor is ok. An alternative would be to pass the factory interface which is capable to produce the default values.
As Jon Skeet said ...
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>(new HashSet<String>());
... however, that would return the same default object as Dunni pointed out.
So this will do:
private static HashMap<String, HashSet<String>> isbnToId = new HashMap<String, HashSet<String>>();
public static void coupleIsbnToId(String isbn, String _id) {
if (!isbnToId.containsKey(isbn)) {
isbnToId.put(isbn, new HashSet<String>());
}
isbnToId.get(isbn).add(_id);
}
Folks,
Is there any easy way to add generic class in non generic class.
Basically the cache manager will have map of Cache class which is implemented with proper generics.
But in below class we return (getCache method) Cache via get method it requires explicit cast at callers place how to avoid it.
e.g.
public class CacheManager {
private Map<String, Cache<?,?>> cacheMap = new HashMap<String, Cache<?,?>>();
public Cache<?,?> getCache(String cacheName) {
return cacheMap.get(cacheName);
}
public void addCache(String cacheName,Cache<?,?> cache) {
cacheMap.put(cacheName, cache);
}
}
Short answer: No (as far as I know).
The problem here is that what you are doing is not type-safe in Java at all. Have a look at this example:
import java.util.*;
class ClassCast {
public static void main(String[] args) {
HashMap<String, Pair<?, ?>> map = new HashMap<>();
map.put("test", new Pair<String, Integer>("Hello", 5));
Pair<Double, Double> pair = (Pair<Double, Double>) map.get("test");
}
}
class Pair<T,V> {
T a;
V b;
Pair(T a, V b) {
this.a = a;
this.b = b;
}
}
You would expect a ClassCastException here, but it compiles and runs perfectly fine. The reason for this is that the actual class of Pair<String, Integer> and Pair<Double, Double> is in fact just Pair (after type erasure).
To get type safety you have to implement the "Typesafe heterogeneous container pattern" (explained in detail in Effective Java by Josh Bloch). In short, you have to involve the type parameter in the key of your map. Depending on your needs, you might be able to use a class as key directly, otherwise you might have to make a key object.
Example implementation:
public class CacheManager {
private Map<MultiKey, Cache<?,?>> cacheMap = new HashMap<>();
#SuppressWarnings("unchecked")
public <T,V> Cache<T,V> get(String name, Class<T> t, Class<V> v) {
// Type-safe since types are encoded in key(i.e. map will not
// return something with the wrong type), and key is type-checked
// on insertion.
return (Cache<T,V>) cacheMap.get(new MultiKey(name, t, v));
}
public <T,V> void put(String name, Class<T> t, Class<V> v, Cache<T,V> cache) {
cacheMap.put(new MultiKey(name, t, v), cache);
}
class MultiKey {
Object[] keys;
Integer hash = null;
MultiKey(Object... keys) {
this.keys = keys;
}
#Override
public int hashCode() {
if (hash == null) hash = Arrays.hashCode(keys);
return hash;
}
#Override
public boolean equals(Object o) {
if (o == null || !(o instanceof MultiKey)) return false;
return Arrays.equals(keys, ((MultiKey) o).keys);
}
}
}
Example usage:
CacheManager mng = new CacheManager();
mng.addCache("SI", String.class, Integer.class, new Cache<String, Integer>());
Cache<String, Integer> cache = mng.getCache("SI", String.class, Integer.class);
System.out.println(cache);
It's not pretty, but it is actually type-safe. It can be improved depending on the actual situation though, so you should not use this code as is. For example, if you can get the types from the Cache object you don't need the Class arguments in addCache.