How to use guava CacheBuilder? - java

I'd like to use guavas CacheBuilder, but cannot find any explicit example how implement this.
The docs state the following code:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
Question: what is this createExpensiveGraph(key) method? Is this a method that returns a HashMap<Key, Value> mapping? What do I have to do with the key value?
Or could I also just return a List<String> in this method not having to use the key value in any way?

The idea of the cache is that you usually have this problem:
Graph get(Key key) {
Graph result = get( key );
if( null == result ) {
result = createNewGraph( key );
put( key, result );
}
return result;
}
plus the usual synchronization issues that you have when you use this code in a multi-threaded environment.
Guava does the boilerplate for you; you just have to implement createNewGraph(). The way Java works, this means you have to implement an interface. In this case, the interface is CacheLoader.
If you think that the whole cache is a bit like a map, then CacheLoader is a callback that gets invoked whenever a key can't be found in the map. The loader will get the key (because it usually contains useful information to create the object that is expected for this key) and it will return the value which get() should return in the future.

Related

HashMap implementation with the soft referenced values

I wanted to have a Map with key mapping to quite a big object.
Since the map is going to be used as a cache, I wanted to make the values/entries referenced via soft links (java.lang.ref.SoftReference) to clear it on pure memory. But in this case, I need to have my own implementation of computeIfAbsent() method.
I could implement it in the following way:
Map<Integer, SoftReference<T>> myMap = new HashMap<>();
public T get(Integer key) {
SoftReference<T> value = myMap.get(key);
if (value == null || value.get() == null) {
value = new SoftReference(retrieveValue());
myMap.put(key, value);
}
return value.get();
}
Just wanted to know, is there an out of the box solution for such a Map, like java.util.WeakHashMap?
Thanks!
Yes, Guava's CacheBuilder supports both SoftReference and WeakReference values, as well as other eviction policies based on size and time. You can use the Cache directly, or view it as a Map:
ConcurrentMap<Integer, V> map = CacheBuilder.newBuilder()
.softValues()
.build()
.asMap()

Set hashMap's value

I am using drools workbench and I have trouble setting a hashmap. I want to avoid adding a method just to add values to the map so I am trying to find a workaround.
When I need to set the value of a list I use:
setList(Arrays.asList("string one", "string two", ...));
I was wondering if such a method exists for hashmaps.
If you are allowed to use 3rd party libs you can use Guava's ImmutableMap
Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");
If you are going to use Map, then definitely you should store the data in from of (Key, Value) pair. Now , in HashMap, you have two methods to store data,
1. put(Object any) - This method takes single object.
2. putAll(Map otherMap) - This method takes some other map, and will add all the elements of that map to yours one.
So If these methods are not suitable for you, then I think you should write your own method to add values. May be you can write as below.
class DroolMap<K,V> extends HashMap<K,V> {
public DroolMap() {
super();
}
public DroolMap(int size) {
super(size);
}
public DroolMap<K, V> add(K key, V value) {
this.put(key, value);
return this;
}
}
class TestDroolMap {
public void testDroolMap() {
DroolMap<String, String> droolMap = new DroolMap<String, String>();
// You can add as many <Key, Value> pairs in one line
droolMap.add("k1", "v1").add("k2", "v2").add("k3", "v3");
}
}

a hashmap with multiple keys to value?

I have a question about hashmaps with multiple keys to value. Let's say I have (key / value )
1/a, 1/b, 1/3, 2/aa, 2/bb, 2/cc.
Would this work?
If it does, could I have a way to loop through it and display all values for only either key 1 or 2?
You can use a map with lists as values, e.g.:
HashMap<Integer, List<String>> myMap = new HashMap<Integer, List<String>>();
java.util.HashMap does not allow you to map multiple values to a single key. You want to use one of Guava's Multimap's. Read through the interface to determine which implemented version is suitable for you.
A simple MultiMap would look something like this skeleton:
public class MultiMap<K,V>
{
private Map<K,List<V>> map = new HashMap<K,List<V>>();
public MultiMap()
{
// Define constructors
}
public void put(K key, V value)
{
List<V> list = map.get(key);
if (list == null)
{
list = new ArrayList<V>();
map.put(key, list);
}
list.add(value);
}
public List<V> get(K key)
{
return map.get(key);
}
public int getCount(K key)
{
return map.containsKey(key) ? map.get(key).size() : 0;
}
}
It cannot directly implement Map<K,V> because put can't return the replaced element (you never replace). A full elaboration would define an interface MultiMap<K,V> and an implementation class, I've omitted that for brevity, as well as other methods you might want, such as V remove(K key) and V get(K key, int index)... and anything else you can think of that might be useful :-)
Maps will handle multiple keys to one value since only the keys need be unique:
Map(key, value)
However one key to multiple values requires s multimap of a map strict of :
Map(key, list(values))
Also, whatever you use as a key really should implement a good hadhCode() function if you decide to use a HashMap and/or HashSet
Edit: had to use() instead of <> because my mobile or sof's mobile site editor clobbered the <> symbols....odd

Java map.get(key) - automatically do put(key) and return if key doesn't exist?

I am sick of the following pattern:
value = map.get(key);
if (value == null) {
value = new Object();
map.put(key, value);
}
This example only scratches the surface of the extra code to be written when you have nested maps to represent a multi-dimensional structure.
I'm sure something somewhere exists to avoid this, but my Googling efforts yielded nothing relevant. Any suggestions?
The
java.util.concurrent.ConcurrentMap
and from Java 8
Java.util.Map
has
putIfAbsent(K key, V value)
which returns the existing value, and if that is null inserts given value. So if no value exists for key returns null and inserts the given value, otherwise returns existing value
If you need lazy evaluation of the value there is
computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
Java 8 adds nice method to the Map: compute, computeIfPresent, computeIfAbsent
To achieve what you need:
Object existingOrCreated = map.computeIfAbsent(key, (k) -> new Object());
The problem with this pattern is that you'd have to somehow define the value that should be used in case the get() returns null.
There certainly are libraries out there and IIRC there are also some newer collections that do that, but unfortunately I don't remember which those were.
However, you could write a utility method yourself, provided you have a standard way of creating the new values. Something like this might work:
public static <K, V> V safeGet(K key, Map<K,V> map, Class<V> valueClass) throws /*add exceptions*/ {
V value = map.get(key);
if( value == null ) {
value = valueClass.newInstance();
map.put( key, value );
}
return value;
}
Note that you'd either have to throw the reflection exceptions or handle them in the method. Additionally, this requires the valueClass to provide a no-argument constructor. Alternatively, you could simply pass the default value that should be used.
Java 8 update
It has already been mentioned in other answers but for the sake of completeness I'll add the information here as well.
As of Java 8 there is the default method computeIfAbsent(key, mappingFunction) which basically does the same, e.g. if the value class was BigDecimal it could look like this:
BigDecimal value = map.computeIfAbsent(key, k -> new BigDecimal("123.456"));
The implementation of that method is similar to the safeGet(...) defined above but more flexible, directly available at the map instance and better tested. So when possible I'd recommend using computeIfAbsent() instead.
You can use MutableMap and getIfAbsentPut() from Eclipse Collections which returns the value mapped to the key or inserts the given value and returns the given value if no value is mapped to the key.
You can either use a method reference to create a new Object:
MutableMap<String, Object> map = Maps.mutable.empty();
Object value = map.getIfAbsentPut("key", Object::new);
Or you can directly create a new Object:
MutableMap<String, Object> map = Maps.mutable.empty();
Object value = map.getIfAbsentPut("key", new Object());
In the first example, the object will be created only if there is no value mapped to the key.
In the second example, the object will be created regardless.
Note: I am a contributor to Eclipse Collections.
If in any case you need to get a default data in your map if it's not existing
map.getOrDefault(key, defaultValue);
javadocs
EDIT : Note that the feature mentioned below is long deprecated, and a CacheBuilder should be used instead.
The Guava library has a "computing map", see MapMaker.makeComputingMap(Function).
Map<String, Object> map = new MapMaker().makeComputingMap(
new Function<String, Object>() {
public String apply(Stringt) {
return new Object();
}
});
If you need the Function several times, extract it into a utility class, and then create the Map like this (where MyFunctions.NEW_OBJECT is the static Function instance):
Map<String, Object> map = new MapMaker()
.makeComputingMap(MyFunctions.NEW_OBJECT);
Maybe I'm not seeing the whole problem, but how about using inheritance or composition to add this behavior to the Map object?

How to make a CaseInsensitiveConcurrentMap?

How can I implement
class CaseInsensitiveConcurrentMap<V> implements ConcurrentMap<String , V>
which works just like ConcurrentHashMap<String , V> except that the keys are compared case-insensitively? The keys should not be converted to lowercase or uppercase.
Note that Collections.synchronizedMap(new TreeMap<String, new MyCaseInsensitiveComparator()) is no solution as it allows no concurrency and misses the additional methods.
Creating a String-like class with case-insensitive equals and hashCode is no option either, since the map has to be passed to methods expecting strings as keys.
Have you tried
ConcurrentMap<String, Object> map =
new ConcurrentSkipListMap<String, Object>(
String.CASE_INSENSITIVE_ORDER);
If you really insist on all your constraints (ConcurrentHashMap) and String keys:
Overwrite any ConcurrentHashMap method and convert any key with key = key.toLowerCase().
Looks like a lot of work, and a lot of unit testing, so if I were you I would try to discuss the interface.
Example:
#Overwrite
public void V get(String key) {
return super.get(transform(key));
}
// Extracted that to avoid cluttering your whole code with that specific transformation
// So you can easily change it
private String transform(String key) {
return key.toLowerCase();
}
Ugly, but, given your constraints, feasible.

Categories