-
Notifications
You must be signed in to change notification settings - Fork 10.9k
MapMakerMigration
All caching related methods on MapMaker
have been deprecated in favor of similar methods in CacheBuilder
, and are scheduled for upcoming deletion. Release 11.0.0 already removed evictionListener
, expireAfterWrite
, and expireAfterAccess
. Future releases will remove makeComputingMap
and expiration
.
Most MapMaker
use cases should be migrated to either CacheBuilder
or AtomicLongMap
. Specifically, cases when MapMaker
is used to construct maps with AtomicLong
values should generally be migrated to AtomicLongMap
. Other cases where MapMaker
caching functionality is used (including all uses of MapMaker.makeComputingMap(Function)
) should be migrated to CacheBuilder
.
Migrating from MapMaker.makeMap()
to CacheBuilder.build()
is trivial, but migrating from MapMaker.makeComputingMap(Function)
to CacheBuilder.build(CacheLoader)
involves some subtle behavioral changes. Specifically, MapMaker.makeComputingMap(Function)
returns a ConcurrentMap
, while CacheBuilder.build(CacheLoader)
returns a LoadingCache
. While LoadingCache
has an asMap()
method, the map returned by that map is much different from the map created by MapMaker
.
MapMaker.makeComputingMap(Function)
returned a magical (and ill-behaved) ConcurrentMap
. Specifically, calls to Map.get(Object)
on the returned map would automatically compute values for absent keys using the specified Function
. These computations would be shared by all concurrent computations on the same key. Such maps were, in effect, autovivification maps.
At first glance this behavior is tremendously useful, but the specific implementation of this functionality behind a plain ConcurrentMap
was riddled with issues.
Having a Map
that auto-creates entries on get
was simply a big mistake. It breaks type-safety (you can use it to store a key in the map that isn't of the map's key type!). Bad things will happen if that Map
accidentally gets passed to another Map
's equals()
method. Common idioms for Map
usage (in the absence of null values) are based on the assumption of interchangeability of containsKey(k)
and (get(k) != null)
, and those coding patterns will break. Etc.
We studied this issue very closely, and concluded that our library will be easier to use when collections are just collections, iterators are just iterators, and things that are fancier than those have public types that convey their behavior sufficiently.
And thus we introduced the LoadingCache
interface. The primary intent of this new interface was to encapsulate a get(K)
method which auto-created entries, while still exposing an asMap()
view which allowed traditional map-style access to the cache internals. In other words, the magical get
from MapMaker.makeComputingMap(Function)
was semantically separated from the other ConcurrentMap
methods. Note that LoadingCache.get(K)
will automatically load absent entries, however LoadingCache.asMap.get(Object)
will not.
The new LoadingCache
interface came with a new builder, CacheBuilder
, patterned after MapMaker
but with an explicit focus on caching, and only capable of producing LoadingCache
(and Cache
) instances, instead of ConcurrentMap
s.
Now that we understand the key distinction between MapMaker
and LoadingCache
we turn to the subject of migrating old code from MapMaker
to CacheBuilder
.
The biggest difference is the change from using a plain Function
to compute values to a more sophisticated CacheLoader
type.
CacheLoader
has a few major differences from Function
:
- Its
load(K key)
method is permitted to throw exceptions. - It provides a
loadAll(Iterable<? extends K>)
method to load multiple keys at once -- possibly concurrently. (By default,loadAll
just sequentially loads each key individually with theload
method.) - It provides a
reload(K key, V oldValue)
method for use in refreshing cached values asynchronously, for caches configured withrefreshAfterWrite
. By default, this synchronously callsload
.
The simplest way to migrate a Function
-based computing map to a CacheLoader
is the CacheLoader.from(Function)
adapter, which views a Function
as a CacheLoader
, no special effort required. That said, it's silly to call CacheLoader.from(new Function<K, V>() {...})
when you can just write
new CacheLoader<K, V>() {
public V load(K key) {
// copy/paste code from Function.apply
}
};
The biggest difference between the computing maps generated by MapMaker.makeComputingMap
and the ConcurrentMap asMap()
view of a Cache
is that the asMap()
view will never compute new values on a call to asMap().get(key)
. This is specifically deliberate to avoid the "magical" unpredictable behavior of computing maps.
- Introduction
- Basic Utilities
- Collections
- Graphs
- Caches
- Functional Idioms
- Concurrency
- Strings
- Networking
- Primitives
- Ranges
- I/O
- Hashing
- EventBus
- Math
- Reflection
- Releases
- Tips
- Glossary
- Mailing List
- Stack Overflow
- Android Overview
- Footprint of JDK/Guava data structures