It's complicated for me to articulate a proper title for this. But an example should make it far simpler. Suppose I have this:
final class Cache {
private static final ConcurrentHashMap<String, List<String>> CACHE = ...
static List<String> byName(String name) {
return CACHE.computeIfAbsent(name, x -> // some expensive operation)
}
}
The idea is probably trivial, this acts as a LoadingCache, much like guava or caffeine (in reality it is more complicated, but that is irrelevant to the question).
I would like to be able to tell if this was the first load into the CACHE, or it was a read of an existing mapping. Currently, I do this:
final class Cache {
private static final ConcurrentHashMap<String, List<String>> CACHE = ...
static List<String> byName(String name) {
boolean b[] = new boolean[1];
List<String> result = CACHE.computeIfAbsent(name, x -> {
b[0] = true;
// some expensive operation)
});
if(b[0]) {
// first load into the cache, do X
} else {
// do Y
}
return result;
}
}
This works, but I am afraid I am missing something that ConcurrentHashMap
can offer for me that would allow me to do the same. Thank you.
If you want to avoid your single-element array to pass data out of the lambda (which I would rather do with an AtomicReference
or AtomicBoolean
), you could use a stateful callback object. It doesn't change the behavior or design of your code, but could be considered a little bit cleaner and more OOP-y.
class LoadingAction<K, V> {
private boolean called = false;
public V load(final K key) {
called = true;
// load data
return ...;
}
public void executePostLoad() {
if (called) {
// loaded into cache, do X
} else {
// do Y
}
}
}
final class Cache {
private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();
static List<String> byName(String name) {
final LoadingAction<String, List<String>> loader = new LoadingAction<>();
final List<String> result = CACHE.computeIfAbsent(name, loader::load);
loader.executePostLoad();
return result;
}
}
Or turn it inside-out:
class Loader<K, V> {
private boolean called = false;
public V load(final Map<K, V> map, final K key) {
final V result = map.computeIfAbsent(key, this::load);
this.executePostLoad();
return result;
}
private V load(final K key) {
called = true;
// load data
return ...;
}
private void executePostLoad() {
if (called) {
// loaded into cache, do X
} else {
// do Y
}
}
}
final class Cache {
private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();
static List<String> byName(String name) {
final Loader<String, List<String>> loader = new Loader<>();
return loader.load(CACHE, name);
}
}
Construction and loading could be encapsulated in a static method:
class Loader<K, V> {
private boolean called = false;
public static <K, V> V load(final Map<K, V> map, final K key) {
final Loader<K, V> loader = new Loader<>();
return loader.doLoad(map, key);
}
private V doLoad(final Map<K, V> map, final K key) {
final V result = map.computeIfAbsent(key, this::load);
this.executePostLoad();
return result;
}
private V load(final K key) {
called = true;
// load data
return ...;
}
private void executePostLoad() {
if (called) {
// loaded into cache, do X
} else {
// do Y
}
}
}
final class Cache {
private static final ConcurrentHashMap<String, List<String>> CACHE = new ConcurrentHashMap<>();
static List<String> byName(String name) {
return Loader.load(CACHE, name);
}
}