JavaのMap.putの戻り値がnullになる理由と上書き前の値を解説する記事のアイキャッチ。

Java クイズ

JavaのMap.putの戻り値がnullになる理由|上書き前の値を確認する方法

AIによる要約

Map.putは追加した新しい値ではなく、そのキーに以前入っていた値を返します。初回登録なら通常null、同じキーを上書きした場合は上書き前の値です。ただし、null値を許すMapでは、未登録と以前の値がnullだった場合を戻り値だけで区別できません。上書きを禁止したいならcontainsKeyやputIfAbsent、既存時だけ更新するならreplaceなど、意図に合うAPIを選びます。

この記事はXへ投稿したJavaクイズの詳しい解説です。まず問題を解きたい方は、Xの元ポストを開くことができます。

新人SE
新人SE
map.put("A", 1)の戻り値が1ではなくnullなのはなぜですか?
ポンコツSE
ポンコツSE
putは入れた値ではなく、そこに入っていた以前の値を返すAPIだからです。

Mapへ値を登録するputは基本APIですが、戻り値を新しい値だと思い込むと処理結果を逆に判断します。戻り値は上書き検知や以前の状態の復元に使えます。

一方、nullを値として許すHashMapでは、初回登録と以前の値nullが同じ戻り値になります。登録、更新、上書き禁止のどれをしたいかを明確にし、put以外のAPIも使い分けます。

この記事のポイント

  • putは上書き前の値を返す
  • 初回登録では通常nullを返す
  • 戻り値nullだけではキー未登録と登録済みnullを区別できない
  • 上書き禁止ならputIfAbsent等を検討する
  • 更新結果を新しい値として使うなら別途getするか変数を使う

まずはXのJavaクイズを確認する

空のHashMapへ同じキーAで1と2をputした戻り値を問うJavaクイズ画像。
同じキーへ2回putすると何が出るでしょうか。
Map<String, Integer> map = new HashMap<>();

System.out.println(map.put("A", 1));
System.out.println(map.put("A", 2));

答えはnull、1です。1回目は以前の値がなく、2回目は以前の1を2で上書きするためです。

Map.putは以前の値を返すためnullと1になると説明する画像。
答えはnull、1です。処理後の値は2になります。

元の投稿は、Xの出題ポスト回答ポストでも確認できます。

putが返すのは以前の値

Map.put(K key, V value)は、登録後の値ではなく、そのキーへ以前関連付けられていた値を返します。メソッド終了後のMapには引数valueが入っています。

Integer previous = map.put("A", 2);
Integer current = map.get("A");

System.out.println(previous); // 1
System.out.println(current);  // 2

戻り値と現在値を同じ変数名で扱うと読み違えます。previousreplacedなど、上書き前だと分かる名前を使います。

初回登録と登録済みnullは区別できない

HashMapへnullを値として登録できるため、putの戻り値がnullでも2通りあります。キーがなかった場合と、キーはあったが以前の値がnullだった場合です。

Map<String, Integer> map = new HashMap<>();
map.put("A", null);

System.out.println(map.put("A", 10)); // null
System.out.println(map.put("B", 10)); // null

登録有無を区別するなら、put前のcontainsKeyを使うか、そもそもMapにnullを入れない設計にします。戻り値だけで『新規登録だった』と断定しないでください。

上書きを禁止したいとき

マスタ登録や一意キーの取込で、既存値を黙って上書きしたくない場合があります。単純なputでは上書き後にしか以前の値を受け取れません。事前確認またはputIfAbsentを検討します。

User existing = users.putIfAbsent(user.id(), user);
if (existing != null) {
    throw new DuplicateUserException(user.id());
}

putIfAbsentはキーに値がない場合に登録します。ただしnull値を含むMapでは挙動の理解が必要です。また、DBの一意制約をMapだけで代替できません。アプリ内重複確認と永続化層の制約を両方用意します。

既存キーだけ更新するならreplace

キーが存在するときだけ更新したいならreplaceで意図を表せます。存在しないキーを新規追加しません。

Integer previous = scores.replace("U001", 90);
if (previous == null) {
    throw new UserNotFoundException("U001");
}

ただし以前の値がnullの場合との曖昧さがあります。Mapのnull方針を先に決め、必要ならcontainsKeyと組み合わせます。条件付きreplaceを使えば、現在値が期待値と一致した場合だけ更新する表現もできます。

集計ではmergeやcomputeを検討する

件数集計のため、getしてnull判定してputするコードは長くなりがちです。mergeを使うと初回値と更新方法を1つの式で表せます。

Map<String, Integer> counts = new HashMap<>();

for (Order order : orders) {
    counts.merge(order.status(), 1, Integer::sum);
}

APIを短く使うことが目的ではありません。初回登録、既存値更新、null時の扱いをチームが理解できるかを確認します。複雑な業務判断をcomputeへ押し込むより、通常のifの方が読みやすい場合もあります。

Map.ofの戻り値へputできない

Map.ofで作ったMapは変更できません。putの戻り値以前に、呼び出し自体がUnsupportedOperationExceptionになります。

Map<String, Integer> fixed = Map.of("A", 1);
fixed.put("A", 2); // UnsupportedOperationException

メソッド引数がMap型でも、具体的な生成方法によって変更可否が異なります。受け取ったMapを書き換えるAPIは、その契約を明示するか、自分の管理下で可変コピーを作ります。

JUnitで登録・更新・nullを分ける

putのテストでは初回登録、既存値上書き、以前の値null、キー未登録を分けます。戻り値と最終的なMapの両方を確認します。

@Test
void 既存値を返して新しい値へ更新する() {
    Map<String, Integer> map = new HashMap<>();
    map.put("A", 1);

    assertEquals(1, map.put("A", 2));
    assertEquals(2, map.get("A"));
}

現場レビューでよくある指摘

Map更新のレビューでは、putの戻り値だけでなく、新規登録、上書き、既存時だけ更新のどれを意図しているかを見ます。

// レビューコメント例
putの戻り値は新しい値ではなく以前の値です。
変数名をpreviousValueへ変更し、現在値は別に扱ってください。

// レビューコメント例
既存データを上書きしてよい仕様か確認できません。
上書き禁止ならputIfAbsentと重複エラーを検討してください。

// レビューコメント例
戻り値nullだけでは未登録と以前の値nullを区別できません。
Mapへnullを許す契約とcontainsKeyの要否を整理してください。

putを別APIへ機械的に置き換えるのではなく、登録と更新の業務ルールを明らかにします。DB更新と同じく、上書きしてよい条件をコードとテストへ残します。

提出前のセルフチェック

レビュー前に確認すること

  • 戻り値が以前の値だと理解しているか
  • 現在値と以前の値の変数名を分けたか
  • 新規登録と更新を区別する必要があるか
  • 上書きを許可する仕様か
  • 登録済みnullと未登録を区別したか
  • putIfAbsent・replace・mergeが意図に合うか
  • 変更不可能なMapへputしていないか
  • 戻り値と最終状態をテストしたか

Map APIを実務で使い分ける参考書

Mapにはput以外にもputIfAbsent、replace、merge、computeがあります。基本と実践を体系的に確認すると、更新意図に合うAPIを選びやすくなります。

書籍「スッキリわかるJava入門 実践編 第5版」の表紙
商品画像:Amazon.co.jp
PR Java基礎学習後の若手向け

スッキリわかるJava入門 実践編 第5版

中山 清喬 (著), 株式会社フレアリンク (監修)

基礎文法の次に必要な、現場寄りのJava知識を補う。

コレクション、ジェネリクス、ラムダ式、ストリームなど、業務コードで出会いやすい機能を入門編の次に学べます。

  • Java基礎の次に何を学ぶか迷っている
  • コレクションやStreamを整理したい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

Map.putは新しい値ではなく、上書き前の値を返します。初回登録では通常null、同じキーを上書きすると以前の値が返り、処理後のMapには新しい値が入ります。

null値を許すMapでは戻り値nullだけで未登録を判断できません。上書きを禁止するならputIfAbsent、既存時だけならreplace、集計ならmergeなど、登録意図に合うAPIを選んでください。

-Java, クイズ
-, , ,