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

时间:2016-05-13 07:17:20

标签: java dictionary java-8 optional

假设我有一个缓存实现为java.util.Map,它存储了键的任意值。由于值不是强制性的,缓存会返回java.util.Optional,并且可以提供java.util.function.Supplier来计算给定的不存在键的值。

我的第一个天真的方法是

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;
    }
}

但我发现这非常不优雅,当我了解java.util.Map#computeIfAbsent时,我将代码更改为以下内容

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);
    }
}

但是现在困扰我的是java.util.Optional#ofNullablenull方法的getValue结果的多余使用,这是为java.util.Map#computeIfAbsent提供“默认” “价值不能插入地图。

在理想情况下,可能会出现以下情况

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());
    }
}

如果第二个参数代表空java.util.Map#computeIfAbsentjava.util.Optional将返回java.util.Optional#empty,那么java.util.Optional#empty会跳过插入,但不幸的是java.util.Map#computeIfAbsent使用String作为“默认”插入值不支持java.util.Optional且代码无法编译。

另一种可能性是存储java.util.Mapjava.util.Optional#empty的映射,但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()); } } 会将Cache1存储为与我的用例相矛盾的值被迫存储无效映射并在以后手动删除/替换它们。

{{1}}

有人知道更好的方法来处理这种用例,还是我必须回到{{1}}的实现?

2 个答案:

答案 0 :(得分:3)

要做这种事情我通常在我的地图中使用Optional - 这样 map.get()!= null表示我已缓存了访问权限并且map.get()。isPresent()告诉我是否返回了合理的值。 在这种情况下,我会使用Suplier&lt; String&gt;当值不存在时返回null。那么实现将如下所示: 公共类Cache {   private final Map&lt; String,Optional&lt; String&gt;&gt; mapping = new HashMap&lt;&gt;();   public可选&lt; String&gt; get(String key,Suplier&lt; String&gt; supplier){     return mapping.computeIfAbsent(key,          未使用 - &gt; Optional.ofNullable(supplier.get()));   } } 缺少的密钥会插入到地图中,但会标记为缺失。

答案 1 :(得分:1)

听起来像你正在重新发明一个Guava LoadingCache(read here about Guava Caches)。虽然这绝对是一项有趣的编程练习,但现有的解决方案经过时间验证,可根据您的需求进行配置,并在极其繁重的负载下工作。

示例定义是:

Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build(); // look Ma, no CacheLoader
...
try {
  // If the key wasn't in the "easy to compute" group, we need to
  // do things the hard way.
  cache.get(key, new Callable<Value>() {
    @Override
    public Value call() throws AnyException {
      return doThingsTheHardWay(key);
    }
  });
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}

这有点等同于您的使用场景,即每个键级别的计算可能不同。通常,您不需要这样做,因此您更喜欢缓存中存储的计算方法:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

...
try {
  return graphs.get(key);
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}