1

The title is very straightforward, I am trying to use caffeine cache for my Minecraft plugin and I can't seem to find any good examples for cache.getAll(). The documentation doesn't help either. All I see is <,>, and ?. I really think that documentation should provide examples. So all I'm asking is if someone could provide an example

Pro Poop
  • 357
  • 5
  • 14
  • When you build the caffeine cache, you are building a type of `Cache` where K is the key type, and V is the value type. Any operation that accepts `? extends K` is telling that method accepts a type/subtype of K. – aksappy Jul 31 '21 at 00:02

2 Answers2

4

Actually using the documentation you should be able to understand what it's expecting as arguments. The author did a very good job. If you don't, I'd suggest you to read on Java generics and understand the syntax.

TL;DR:

Cache<String, Integer> cache = Caffeine.newBuilder().build();

cache.getAll(List.of("one", "two", "three"), keys -> {
    Map<String, Integer> result = new HashMap<>();
    keys.forEach(key -> {
        if (key.equals("one")) result.put(key, 1);
        else if (key.equals("two")) result.put(key, 2);
        else if (key.equals("three")) result.put(key, 3);
    });
    return result;
});

The example might not make much sense in terms of functionality, but it's an example.

The long version

The documentation has a method signature of:

Map<K,​V> getAll​(Iterable<? extends K> keys, Function<? super Set<? extends K>,​? extends Map<? extends K,​? extends V>> mappingFunction)

Let's break this down.

Map<K,​V> getAll​

The getAll method returns a Map with a key of type K and a value of type V. If your cache implementation is something like:

Cache<String, Integer> cache = Caffeine.newBuilder().build();

then K == String and V == Integer

The first argument of that method is:

Iterable<? extends K> keys

Now Iterable is a JDK interface, which is implemented by pretty much all of the Collections. Check this for "known" implementing classes. The question mark actually means "any type that extends K" (where K is String in the example)

So as a first argument we can use something like:

List.of("one", "two")

Function<? super Set<? extends K>,​? extends Map<? extends K,​? extends V>>

That might look confusing but it isn't if you try to break it down again. Function is an interface (that declares only one method, hence functional) that declares as the first type, the type of the input and as the second type, the type of the return. More info here.

So ? super Set<? extends K> is any type that is a superset of Set with elements of type K (aka String in the example). Why the author opted for the super keyword? Google what "PECS" is (producer extends, consumer super).

And then ​? extends Map<? extends K,​? extends V> is as you have figured out, an implementation of Map with keys of type K and values of type V (or Integer).

Notes

Returns a map of the values associated with the keys, creating or retrieving those values if necessary. The returned map contains entries that were already cached, combined with the newly loaded entries; it will never contain null keys or values.

Self explanatory but note that the returned map would have all cache values and your mappingFunction's results combined - without null keys/values

A single request to the mappingFunction is performed for all keys which are not already present in the cache

Your mappingFunction will be called only once, with a list of all the keys that were requested but not found in the cache.

All entries returned by mappingFunction will be stored in the cache, over-writing any previously cached values

Self explanatory as well, the map that the mappingFunction returns will replace or be stored in cache

Skod
  • 435
  • 6
  • 16
1

The user-guide provides a simple examples of this functionality. A getAll method is available in all flavors of the cache.

Synchronous cache

In this abstraction, the caller will block waiting for the loads to complete

Cache<Key, Graph> cache = Caffeine.newBuilder().build();
cache.put(k1, v1);

// Loads k2 & k3 (uses k1)
Map<Key, Graph> graphs = cache.getAll(Set.of(k1, k2, k3),
    keys -> createExpensiveGraphs(keys));

If you wish to decouple the caller from the loading function, then create a LoadingCache. If the supplied CacheLoader does not implement bulk functionality, then it will fallback to one-by-one loading.

LoadingCache<Key, Graph> cache = Caffeine.newBuilder()
    .build(CacheLoader.bulk(keys -> createExpensiveGraphs(keys)));
Map<Key, Graph> graphs = cache.getAll(keys);

Note that in this abstraction loads are non-blocking. If a getAll is retrieving k2 and a blocking get(k2) occurs concurrently then both loads will be in-flight. When the getAll completes it will overwrite the existing value. This behavior is because we cannot lock multiple entries in ConcurrentHashMap compute methods, but that concern is resolved if using an AsyncCache.

Asynchronous Cache

In this abstraction the cache stores a mapping to a CompletableFuture and returns that to the caller. You can either call with a synchronous function that wraps as asynchronous, or use an asynchronous function that returns the futures directly.

AsyncCache<Key, Graph> cache = Caffeine.newBuilder().buildAsync();
CompletableFuture<Map<Key, Graph>> graphs1 = cache.getAll(Set.of(k1, k2, k3),
    keys -> createExpensiveGraphs(keys));
CompletableFuture<Map<Key, Graph>> graphs2 = cache.getAll(Set.of(k1, k2, k3),
    (keys, executor) -> createExpensiveGraphFutures(keys));

The loading logic can be hidden from the caller using an AsyncLoadingCache.

AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()
    .build(AsyncCacheLoader.bulk(keys -> createExpensiveGraphs(keys)));
CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

In this abstraction getAll will block subsequent get calls for the same key. This is due to the underlying map holding an in-flight future entry, so placeholders are inserted and completed when the bulk operation is done.

Ben Manes
  • 9,178
  • 3
  • 35
  • 39