I try to make a cache using MapMaker/CacheBuilder but I don't understand how to properly handle null values.
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.weakKeys()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.makeComputingMap(
new Function<Key, Graph>() {
public Graph apply(Key key) {
return createExpensiveGraph(key);
}
});
If the method createExpensiveGraph returns a null value, then a NullpointerException is thrown. I don't understand why the ComputingConcurrentHashMap throws a NPE instead of just returning a null value.
How to properly handle this ? Just catch the NPE and return null instead ? Am I missing something ?
Guava tries to force you to avoid using null
wherever possible, because improper or undocumented behavior in the presence of null
can cause a huge amount of confusion and bugs. I think it's definitely a good idea to avoid using nulls wherever possible, and if you can modify your code so that it doesn't use null
, I strongly recommend that approach instead.
The answer to your question critically depends on what a "null" value actually means in your application. Most likely, it means that there's "no value" for this key, or "nothing there." In this case, probably your best bet is to use Optional
, wrapping non-null values with Optional
.of and using Optional.absent()
instead of null
. If you must turn that into a null or non-null value, you can use Optional.orNull()
.
Notice how even in the full statement of your question it's still not clear whether your intent is to have that null value cached or not. Whether CacheBuilder decided to cache null values or not, it would surprise many users who expected the opposite. Once again, null
creates ambiguities (that's what it's best at!).
So here's what you do.
Is it possible to determine that the answer will be "null" without the full expense of createExpensiveGraph
? i.e., is there really just a simple precondition check going on? If so, you should do that before asking the cache at all, at which point the question of whether the result should be cached or not just goes away.
Do you want to cache a null value? Then follow Louis's advice to use Optional<T>
.
Otherwise, the cache failed to do its job of summoning a correct value for the key, and the appropriate response is to throw an exception from your cache loader (unchecked if this is programmer error, checked otherwise). And that will provide the behavior you want. If you throw a checked exception, make sure you're using Cache.get
, not Cache.getUnchecked
, on the other side.
See LivingWithNullHostileCollections on the Guava wiki for some ideas on how to deal with this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With