Smutje Smutje - 6 months ago 75
Java Question

Java 8 java.util.Map#computeIfAbsent with java.util.Optional

Suppose I have a cache implemented as

java.util.Map
which stores (arbitrary) values for keys. As the values are not mandatorily present, the cache returns an
java.util.Optional
and is able to be provided with a
java.util.function.Supplier
to calculate the value for a given non-existing key.

My first naive approach was

public class Cache0 {

private final Map<String, String> mapping = new HashMap<>();

public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final Optional<String> valueOptional;

if (this.mapping.containsKey(key)) {
final String value = this.mapping.get(key);

valueOptional = Optional.of(value);
} else {
valueOptional = supplier.get();

if (valueOptional.isPresent()) {
this.mapping.put(key, valueOptional.get());
}
}

return valueOptional;
}
}


but I found this very inelegant and as I learned about
java.util.Map#computeIfAbsent
I changed the code to the following

public class Cache1 {

private final Map<String, String> mapping = new HashMap<>();

public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final String value = this.mapping.computeIfAbsent(key, absentKey -> this.getValue(supplier));

return Optional.ofNullable(value);
}

private String getValue(Supplier<Optional<String>> supplier) {
return supplier.get()
.orElse(null);
}
}


but what now bothers me is the redundant use of
java.util.Optional#ofNullable
in combination with the
null
result of the
getValue
method which is needed to provide
java.util.Map#computeIfAbsent
with the "default" value not to be inserted into the map.

In an ideal situation, something like the following would be possible

public class Cache2 {

private final Map<String, String> mapping = new HashMap<>();

public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}


where
java.util.Map#computeIfAbsent
would skip the insertion if the second parameter represents an empty
java.util.Optional
and returns an
java.util.Optional#empty
instead but unfortunately the use of
java.util.Optional#empty
as "default" insert value for
java.util.Map#computeIfAbsent
is not supported and the code does not compile.

A further possibility would be to store a mapping of
String
to
java.util.Optional
but then the
java.util.Map
would store the
java.util.Optional#empty
as value contradicting my use-case again to be forced to store invalid mappings and removing/replacing them by hand later.

public class Cache3 {

private final Map<String, Optional<String>> mapping = new HashMap<>();

public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}


Is anyone aware of a better approach to handle this kind of use-case or do I have to fall back to my implementation of
Cache1
?

Answer

To do this kind of thing I usually use an Optional in my map - this way map.get()!=null means I've cached the access and map.get().isPresent() tells me if a sensible value was returned.

In this case I'd use a Suplier<String> that returns null when the value is not present. Then the implementation would look like this:

public class Cache {
  private final Map<String, Optional<String>> mapping = new HashMap<>();

  public Optional<String> get(String key, Suplier<String> supplier) {
    return mapping.computeIfAbsent(key, 
         absentKey -> Optional.ofNullable(supplier.get(key)) );
  }
}

Absent keys do get inserted into the map, but marked as missing.