Java 逆引き集 | Map のビュー(keySet, values, entrySet)の使い分け — 効率的操作

Java Java
スポンサーリンク

Map のビュー(keySet, values, entrySet)の使い分け — 効率的操作

Map の「ビュー」は中身をコピーせず、元の Map に直接つながった見え方を返します。ビュー経由での操作は元の Map に即反映され、逆も同様です。目的(キー操作・値操作・ペア操作)に合わせて、正しく使い分けると効率的で安全に扱えます。


3つのビューの性質と違い

  • keySet(): キーの集合ビュー(Set<K>)
    • 用途: キー中心の操作(存在確認、削除、フィルタ)。
    • 操作が反映: keySet.remove(k) → Map の (k, v) が削除される。
    • 制約: 値への直接アクセスは不可。値を扱う場合は Map#get が必要。
  • values(): 値のコレクションビュー(Collection<V>)
    • 用途: 値中心の走査・集計(最大・最小・合計・フィルタ)。
    • 操作が反映: values.remove(v) → 該当値の (k, v) が削除される(重複値注意)。
    • 制約: キーが分からない。値だけを扱う用途に限定。
  • entrySet(): キーと値のペアビュー(Set<Map.Entry<K,V>>)
    • 用途: 最も汎用。キー・値の両方を同時に扱う更新・削除・走査に最適。
    • 性能: 反復時に Map#get が不要(キー・値が直に取れる)ため、キー→値取得より効率的。
    • 編集: entry.setValue(newV) で値の更新ができる(キーの変更は不可)。

すぐ試せる基本例

keySet で削除・フィルタ

import java.util.*;

Map<String, Integer> m = new HashMap<>(Map.of("A",1, "B",2, "C",3));

// キーで削除
m.keySet().remove("B");              // B が消える

// 条件でキーを一括削除
m.keySet().removeIf(k -> k.startsWith("C")); // C が消える

System.out.println(m); // {A=1}
Java

values で値の集計・削除

Map<String, Integer> m = new HashMap<>(Map.of("A",10, "B",20, "C",20));

// 全値の合計
int sum = m.values().stream().mapToInt(Integer::intValue).sum(); // 50

// 値で削除(重複に注意)
m.values().remove(20); // B か C のどちらか1件が消える

System.out.println(m); // {A=10, C=20}(例)
Java

entrySet で効率的に更新・削除

Map<String, Integer> m = new HashMap<>(Map.of("A",1, "B",2, "C",3));

// 値を二倍に更新(get 不要で直接)
for (Map.Entry<String, Integer> e : m.entrySet()) {
    e.setValue(e.getValue() * 2);
}

// 条件でエントリ削除
m.entrySet().removeIf(e -> e.getKey().equals("B"));

System.out.println(m); // {A=2, C=6}
Java

例題で理解する使い分け

例題1: 「キーで選別→値で集計」

Map<String, Integer> sales = new HashMap<>(Map.of(
    "tokyo", 100, "osaka", 80, "nagoya", 60, "toyama", 40));

// 都市名が o を含むものだけ残す(キーでフィルタ)
sales.keySet().removeIf(k -> !k.contains("o"));

// 残った値の平均(値のビュー)
double avg = sales.values().stream().mapToInt(Integer::intValue).average().orElse(0.0);

System.out.println(sales); // {tokyo=100, osaka=80, toyama=40}
System.out.println(avg);   // 73.333...
Java
  • ねらい: キー中心の選別は keySet、集計は values。

例題2: 条件に応じて値を更新(entrySet)

Map<String, Integer> score = new HashMap<>(Map.of("Alice",70, "Bob",85, "Cara",90));

for (var e : score.entrySet()) {
    if (e.getValue() < 80) e.setValue(e.getValue() + 10); // 80未満にボーナス加点
}

System.out.println(score); // {Alice=80, Bob=85, Cara=90}
Java
  • ねらい: 値更新は entrySet が最短・最効率。

例題3: 値だけで削除するときの注意(values)

Map<String, String> tags = new HashMap<>(Map.of("p1","java", "p2","stream", "p3","java"));

// 「java」タグを1件だけ消す(どのキーが消えるかは不定)
tags.values().remove("java");
System.out.println(tags); // 例: {p2=stream, p3=java}
Java
  • ねらい: values.remove は「最初に見つかった値」を削除。どのキーかは確定しない。複数削除は removeIf を使うか entrySet で条件指定。

実用レシピ(安全・効率)

キーで存在チェック→必要なら削除(keySet)

Set<String> keys = m.keySet();
if (keys.contains("obsolete")) keys.remove("obsolete");
Java

値の正規化・更新(entrySet+setValue)

for (var e : m.entrySet()) {
    e.setValue(normalize(e.getValue()));
}
Java

条件で複数削除(entrySet.removeIf)

m.entrySet().removeIf(e -> e.getKey().startsWith("tmp_") || e.getValue() == null);
Java

反復の定番(効率重視は entrySet)

for (var e : m.entrySet()) {
    String k = e.getKey();
    Integer v = e.getValue();
    // k と v を同時に使う処理
}
Java

テンプレート集(そのまま使える形)

  • キー一覧・削除
Set<K> keys = map.keySet();
keys.removeIf(this::removeKeyCond);
Java
  • 値一覧・集計
Collection<V> vals = map.values();
int sum = vals.stream().mapToInt(this::toInt).sum();
Java
  • キー・値を同時に扱う(更新・削除)
for (Map.Entry<K,V> e : map.entrySet()) {
    if (shouldUpdate(e)) e.setValue(update(e.getValue()));
}
map.entrySet().removeIf(this::removeEntryCond);
Java
  • entrySet を Stream で処理
map.entrySet().stream()
   .filter(e -> cond(e.getKey(), e.getValue()))
   .forEach(e -> e.setValue(transform(e.getValue())));
Java

落とし穴と回避策

  • ビューはコピーではない: ビューの変更は Map に反映される。外部に「読み取り専用」で渡したいなら Collections.unmodifiableMap(map) を返す。
  • values でキーが分からない: キーとセットで扱うなら entrySet。values 経由の削除は「どのキーか特定しづらい」ことを理解して使う。
  • 拡張 for 中の構造変更: ビューで反復中に Map を別経路で add/remove すると不整合・例外の原因。ビュー側の removeIfiterator.remove を使う。
  • キー更新はできない: Entry の key は変更不可。キーを変えたい場合は「新規 put → 旧キー remove」の2段階で。
  • 並行環境: 通常の Map ビューはスレッドセーフではない。並行用途は ConcurrentHashMap を選び、forEachcompute/merge を活用する。

まとめ

  • keySet は「キー中心の操作」、values は「値中心の集計・削除」、entrySet は「キーと値を同時に扱う更新・反復」に最適。
  • 更新や効率重視の反復は entrySet が定番。値の集計は values が簡潔、キーの選別・削除は keySet が直感的。
  • ビューの変更は Map に直結する性質を理解し、目的に合わせてビューを選ぶと、短く読みやすく、正確で効率的なコードになる。
タイトルとURLをコピーしました