ホームページ  >  記事  >  Java  >  Map.merge() の詳細な紹介 (コード付き)

Map.merge() の詳細な紹介 (コード付き)

不言
不言転載
2019-03-22 17:04:096013ブラウズ

この記事では、Map.merge() の詳細な紹介 (コード付き) を紹介します。これには一定の参考値があります。必要な友人は参照できます。お役に立てば幸いです。

今日は Map のマージ メソッドを紹介し、その威力を見てみましょう。

JDK API では、このようなメソッドは非常に特殊で、非常に斬新なので、時間をかけて理解する価値があります。また、実際のプロジェクト コードに適用することをお勧めします。とても助かりました。 Map.merge())。これはおそらく Map 上で最も多用途な操作です。しかし、これはあまり知られていないため、使用している人はほとんどいません。

背景紹介

merge() は次のように説明できます。キーに新しい値を割り当てるか (キーが存在しない場合)、既存のキーを指定された値で更新します (UPSERT)。 。最も基本的な例、つまり一意の単語の出現数をカウントすることから始めましょう。 Java 8 より前は、コードは非常に複雑で、実際の実装では本質的な設計上の意味が失われていました。

var map = new HashMap<String, Integer>();
words.forEach(word -> {
    var prev = map.get(word);
    if (prev == null) {
        map.put(word, 1);
    } else {
        map.put(word, prev + 1);
    }
});

上記のコードのロジックによれば、入力セットが与えられたと仮定すると、出力結果は次のようになります;

var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz");
//...
{Bar=1, Fizz=2, Foo=3, Buzz=2}

V1

次に、それを再構築してみましょう。 、主に判定ロジックの一部を削除;

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.put(word, map.get(word) + 1);
});

このような改善により、再構築の要件を満たすことができます。 putIfAbsent() の具体的な使用法については詳しく説明しません。コード行 putIfAbsent は必ず必要です。そうでないと、後続のロジックでエラーが報告されます。以下のコードでは、put と get が再度出現するのが奇妙ですが、引き続き設計を改善していきます。

V2

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.computeIfPresent(word, (w, prev) -> prev + 1);
});

computeIfPresent を改善すると、単語にキーが存在する場合にのみ指定された変換が呼び出されます。それ以外の場合は何も処理しません。キーをゼロに初期化することでキーが存在することを確認するため、増分は常に有効になります。この実装は十分に完璧ですか?必ずしも必要というわけではありませんが、追加の初期化を減らすための他のアイデアもあります。

words.forEach(word ->
        map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1)
);

compute () は computeIfPresent() に似ていますが、指定されたキーの有無に関係なく呼び出されます。 key の値が存在しない場合、prev パラメータは null になります。単純な if をラムダに隠された三項式に移動することも、最適なパフォーマンスからは程遠いです。最終バージョンを紹介する前に、Map.merge() のデフォルト実装の少し簡略化したソース コード分析を見てみましょう。

V3 の改善

merge() ソース コード
default V merge(K key, V value, BiFunction<V, V, V> remappingFunction) {
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if (newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

コード スニペットは千の言葉に匹敵します。ソース コードを読むことで、いつでも新しい発見ができるので、merge() は両方の状況に適しています。指定されたキーが存在しない場合は、put(key, value) になります。ただし、キーにすでに値が含まれている場合、remappingFunction はマージを選択できます。この機能は上記のシナリオに最適です:

  • 新しい値を返して古い値を上書きするだけです: (old, new) -> new
  • 古い値を保持するには、古い値を返すだけです: (old, new) -> old
  • 次のような方法で 2 つをマージします: (old, new) - > old new
  • 古い値も削除します: (old, new) -> null

ご覧のとおり、マージされています。 () は非常に一般的です。そこで、私たちの質問は、merge() をどのように使用するかということです。コードは次のとおりです:

words.forEach(word ->
        map.merge(word, 1, (prev, one) -> prev + one)
);

次のように理解できます: キーがない場合、初期化された値は 1 に等しく、そうでない場合は既存の値に 1 が追加されます。このシナリオではデフォルトは常にプラス 1 であり、特定の変更は自由に切り替えることができるため、コード内の 1 は定数です。

シナリオ

想像してみてください、merge() は本当に使いやすいのでしょうか?そのシーンは何でしょうか?

例として。口座操作クラス

class Operation {
    private final String accNo;
    private final BigDecimal amount;
}

と、さまざまな口座に対する一連の操作

operations = List.of(
    new Operation("123", new BigDecimal("10")),
    new Operation("456", new BigDecimal("1200")),
    new Operation("123", new BigDecimal("-4")),
    new Operation("123", new BigDecimal("8")),
    new Operation("456", new BigDecimal("800")),
    new Operation("456", new BigDecimal("-1500")),
    new Operation("123", new BigDecimal("2")),
    new Operation("123", new BigDecimal("-6.5")),
    new Operation("456", new BigDecimal("-600"))
);

があるとします。各口座の残高 (合計操作額) を計算したいと考えています。 merge() を使用しない場合、非常に面倒になります。

Map balances = new HashMap<String, BigDecimal>();
operations.forEach(op -> {
    var key = op.getAccNo();
    balances.putIfAbsent(key, BigDecimal.ZERO);
    balances.computeIfPresent(key, (accNo, prev) -> prev.add(op.getAmount()));
});

マージ後のコードを使用します

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), 
                (soFar, amount) -> soFar.add(amount))
);

最適化のロジック。

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);

もちろん結果は正しいですが、このような簡潔なコードは興味深いでしょうか?各操作について、add には、指定された amountaccNo が与えられます。

{ 123 = 9.5,456 = - 100 }

ConcurrentHashMap

ConcurrentHashMap まで拡張すると、Map.merge が登場すると、ConcurrentHashMap との組み合わせは非常に完璧です。この一致シナリオは、挿入または更新操作を自動的に実行するシングルスレッドセーフなロジック用です。

この記事はここで終了しています。その他のエキサイティングなコンテンツについては、PHP 中国語 Web サイトの Java チュートリアル ビデオ 列に注目してください。

以上がMap.merge() の詳細な紹介 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。