この記事では、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 が再度出現するのが奇妙ですが、引き続き設計を改善していきます。
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
(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
には、指定された amount
で accNo
が与えられます。
{ 123 = 9.5,456 = - 100 }
ConcurrentHashMap
ConcurrentHashMap まで拡張すると、Map.merge が登場すると、ConcurrentHashMap との組み合わせは非常に完璧です。この一致シナリオは、挿入または更新操作を自動的に実行するシングルスレッドセーフなロジック用です。
この記事はここで終了しています。その他のエキサイティングなコンテンツについては、PHP 中国語 Web サイトの Java チュートリアル ビデオ 列に注目してください。
以上がMap.merge() の詳細な紹介 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。