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}
Javavalues で値の集計・削除
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}(例)
JavaentrySet で効率的に更新・削除
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 すると不整合・例外の原因。ビュー側の
removeIfやiterator.removeを使う。 - キー更新はできない: Entry の key は変更不可。キーを変えたい場合は「新規 put → 旧キー remove」の2段階で。
- 並行環境: 通常の Map ビューはスレッドセーフではない。並行用途は
ConcurrentHashMapを選び、forEachやcompute/mergeを活用する。
まとめ
- keySet は「キー中心の操作」、values は「値中心の集計・削除」、entrySet は「キーと値を同時に扱う更新・反復」に最適。
- 更新や効率重視の反復は entrySet が定番。値の集計は values が簡潔、キーの選別・削除は keySet が直感的。
- ビューの変更は Map に直結する性質を理解し、目的に合わせてビューを選ぶと、短く読みやすく、正確で効率的なコードになる。
