Java 8 java.util.Map#computeIfAbsent with java.util.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#ofNullable的冗余使用与getValue方法的null结果相结合,该方法需要为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.Optional并返回java.util.Optional#empty,则java.util.Map#computeIfAbsent将跳过插入,但不幸的是,不支持使用 java.util.Optional#empty 作为java.util.Map#computeIfAbsent的"默认"插入值,并且代码无法编译。

另一种可能性是存储Stringjava.util.Optional的映射,但随后java.util.Map会将java.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实现?

要做这种事情,我通常在地图中使用 Optional - 这样 map.get()!=null表示我已经缓存了访问权限,map.get().isPresent()告诉我是否返回了合理的值。

在这种情况下,我会使用一个Suplier<String>,当值不存在时返回null。然后实现将如下所示:

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, 
         unused -> Optional.ofNullable(supplier.get()) );
  }
}

缺少的键确实会插入到地图中,但标记为缺失。

在我看来,您正在重新发明番石榴加载缓存(阅读此处有关番石榴缓存的信息(。虽然这绝对是一个有趣的编程练习,但现有的解决方案是经过时间验证的,可以根据您的需求进行配置,并在极重的负载下工作。

一个示例定义是:

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

相关内容

最新更新