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


115

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

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


そこの例からあなたが何を理解していないのか分かりませんか?
Louis Wasserman 2013年

2
「k」とは何ですか?定義されている変数ですか?「新しい値」はどうですか-それはJava 8からのものですか、それとも定義またはオーバーライドする必要があるオブジェクトを表しているのですか?whoLetDogsOut.computeIfAbsent(key、k-> new Boolean(tryToLetOut(k)))がコンパイルされないため、何か不足しています...
Benjamin H

正確に何がコンパイルされないのですか?どのようなエラーが発生しますか?
axtavt 2013年

Temp.java:26:エラー:式の開始が不正ですwhoLetDogsOut.computeIfAbsent(key、k-> new Boolean(tryToLetOut(k))); (「>」を指す)
Benjamin H

私のためにうまくコンパイルします。実際にJava 8コンパイラを使用していることを確認してください。他のJava 8機能は動作しますか?
axtavt 2013年

回答:


97

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

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

次にcreating a value for "snoop"、2回目の呼び出し時と同じように、computeIfAbsentそのキーの値がすでにあるので、メッセージが1回だけ表示されます。kラムダ式ではk -> f(k)ちょうどマップが値を計算するために、あなたのラムダに渡すキーのplaceolder(パラメータ)です。したがって、この例では、キーは関数呼び出しに渡されます。

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

近い例にあなたの質問にあるように、あなたはそれを書くことができるwhoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(あなたが、パラメータに名前を付けるかどうかは関係ありませんkkey)。それともとしてそれを書くwhoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);場合tryToLetOutであるstaticwhoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);場合は、tryToLetOutインスタンスメソッドです。


114

最近、私もこの方法で遊んでいました。フィボナッチ数を計算するためのメモ化されたアルゴリズムを作成しました。これは、メソッドの使用方法のもう1つの例として役立ちます。

私たちは、マップを定義し、基本ケースのためにそれに値を入れて起動することができ、すなわち、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は、提供されたラムダ式を使用して、数値がマップに存在しない場合にフィボナッチ数を計算します。これは、従来のツリー再帰アルゴリズムを大幅に改善したものです。


18
ダイナミックプログラミングへの1行の優れた変換。非常に滑らかです。
Benjamin H

3
最初に(n-2)呼び出しがある場合、再帰呼び出しが少なくなる可能性がありますか?
するThorbjörnRavnアンデルセン

9
computeIfAbsentを再帰的に使用する場合は、さらに注意が必要です。詳細については、stackoverflow.com
questions /

11
このコードHashMapは、bugs.openjdk.java.net / browse / JDK-8172951と同様に、の内部を破壊し、ConcurrentModificationExceptionJava 9(bugs.openjdk.java.net/browse/JDK-8071667)で失敗します
Piotr Findeisen

22
ドキュメントは文字通り、マッピング関数が計算中にこのマップを変更してはならないことを言っているので、この答えは明らかに間違っています。
FPS

41

もう一つの例。マップの複雑なマップを作成する場合、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

マルチマップ

これは 、Google Guavaライブラリを使用せずにマルチマップを作成する場合に非常に役立ちます。MultiMap

たとえば、特定の科目に登録した学生のリストを保存するとします。

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を使用すると、次のようにグアバマルチマップなしで1行で書き込むことができます。

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

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


Java 8(およびより簡潔)でマルチマップを作成するもう1つの方法は、次のようにすることです。studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());これにより、Map<T,List<T>JDK でタイプのマルチマップがより簡潔に作成されます。
ゾンビ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.