簡體   English   中英

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

[英]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 ,當我了解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.Map#computeIfAbsent將跳過插入,並返回一個java.util.Optional#empty但不幸的是將java.util.Optional#empty用作“默認值” “不支持java.util.Map#computeIfAbsent插入值,並且代碼無法編譯。

另一種可能性是存儲Stringjava.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實現?

為此,我通常在地圖中使用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()) );
  }
}

缺少的鍵確實會插入到地圖中,但標記為丟失。

在我看來,您正在重新發明一個Guava LoadingCache( 在此處閱讀有關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());
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM