web-dev-qa-db-ja.com

新しいcomputeIfAbsent関数を使用するにはどうすればよいですか?

Map.computeIfAbsent を使用したいのですが、ラムダが学部生になってから長すぎます。

ほとんどのドキュメントから直接:それは物事を行うための古い方法の例を提供します:

Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
  Boolean isLetOut = tryToLetOut(key);
  if (isLetOut != null)
    map.putIfAbsent(key, isLetOut);
}

そして新しい方法:

map.computeIfAbsent(key, k -> new Value(f(k)));

しかし、彼らの例では、私はまったく「理解」していないと思います。これを表現する新しいラムダ式を使用するようにコードを変換するにはどうすればよいですか?

84
Benjamin H

次のコードがあるとします:

import Java.util.Map;
import Java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("creating a value for \""+s+'"');
        return s.isEmpty();
    }
}

その後、computeIfAbsentの2回目の呼び出しで既にそのキーの値が存在するため、メッセージcreating a value for "snoop"が1回だけ表示されます。ラムダ式k -> f(k)kは、値を計算するためにマップがラムダに渡すキーの単なるプレースホルダー(パラメーター)です。したがって、この例では、キーは関数呼び出しに渡されます。

または、ヘルパーメソッドなしで同じ結果を得るためにwhoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());と書くこともできます(ただし、デバッグ出力は表示されません)。さらに簡単なのは、既存のメソッドへの単純な委任であるため、次のように記述できます。whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);この委任には、パラメーターを記述する必要はありません。

質問の例に近づけるために、whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));と書くことができます(パラメーターにkまたはkeyという名前を付けても構いません)。または、tryToLetOutstaticの場合はwhoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);、またはtryToLetOutがインスタンスメソッドの場合はwhoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);として記述します。

81
Holger

最近、私もこの方法で遊んでいました。私は、フィボナッチ数を計算するためのメモ化されたアルゴリズムを書きました。

最初に、マップを定義し、ベースケース(fibonnaci(0)fibonacci(1))のマップに値を設定します。

private static Map<Integer,Long> memo = new HashMap<>();
static {
   memo.put(0,0L); //fibonacci(0)
   memo.put(1,1L); //fibonacci(1)
}

そして、帰納的なステップのために、フィボナッチ関数を次のように再定義するだけです。

public static long fibonacci(int x) {
   return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

ご覧のとおり、メソッドcomputeIfAbsentは、指定されたラムダ式を使用して、数値がマップにない場合にフィボナッチ数を計算します。これは、従来のツリー再帰アルゴリズムに比べて大幅に改善されています。

93
Edwin Dalorzo

もう一つの例。地図の複雑な地図を作成する場合、computeIfAbsent()メソッドは地図のget()メソッドの代わりになります。 computeIfAbsent()呼び出しをチェーン化することにより、提供されているラムダ式により、欠落しているコンテナがその場で構築されます。

  // Stores regional movie ratings
  Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();

  // This will throw NullPointerException!
  regionalMovieRatings.get("New York").get(5).add("Boyhood");

  // This will work
  regionalMovieRatings
    .computeIfAbsent("New York", region -> new TreeMap<>())
    .computeIfAbsent(5, rating -> new TreeSet<>())
    .add("Boyhood");
31
hexabc

これは、グアバライブラリを使用せずにマルチマップを作成する場合に非常に役立ちます( https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap。 html

例:特定の科目に登録した学生のリストを保存する場合。 jdkライブラリを使用したこれの通常の解決策は

Map<String,List<String>> studentListSubjectWise = new TreeMap<>();
List<String>lis = studentListSubjectWise.get("a");
if(lis == null) {
    lis = new ArrayList<>();
}
lis.add("John");

//continue....

ボイラープレートコードがあるため、人々はguava Mutltimapを使用する傾向があります。

Map.computeIfAbsentを使用すると、次のようにguava Multimapなしで1行で記述できます。

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

スチュアート・マークスとブライアン・ゲッツはこれについて良い話をしました https://www.youtube.com/watch?v=9uTVXxJjuco

18
nantitv