1.

Java で Map のキーと値を取得する|entrySet と getOrDefault

編集
この記事の要点
  • キーと値の両方が要るなら entrySet()keySet()get() は 2 度探すので遅い
  • 無いキーの get()nullgetOrDefault() なら分岐が要らない
  • keySet() / values()元の Map と連動するビュー。削除すると元も消える
  • 走査しながら削除するなら Iterator.remove()removeIf()
  • 値からキーを引くには全走査が要る。頻繁にやるなら逆引きの Map を用意する

3 つの取り出し方

Map<String, Integer> scores = new LinkedHashMap<>();
scores.put("math", 80);
scores.put("english", 70);

// 1. キーと値の組
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " = " + e.getValue());
}

// 2. キーだけ
for (String key : scores.keySet()) {
    System.out.println(key);
}

// 3. 値だけ
for (Integer value : scores.values()) {
    System.out.println(value);
}

// ラムダで書く
scores.forEach((k, v) -> System.out.println(k + " = " + v));
// 遅い書き方: キーを回してから毎回 get で探し直している
for (String key : scores.keySet()) {
    System.out.println(key + " = " + scores.get(key));
}

// 速い書き方: 組をそのまま受け取る
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " = " + e.getValue());
}

件数が少なければ差は出ませんが、数万件を回すと entrySet() のほうが明確に速くなります

1 件だけ取り出す

Integer v1 = scores.get("math");                 // 80
Integer v2 = scores.get("science");              // null

int v3 = scores.getOrDefault("science", 0);      // 0(推奨)

// int で受けると null で落ちる
// int v4 = scores.get("science");               // NullPointerException

// 値が null なのかキーが無いのかを区別する
if (scores.containsKey("science")) { }

// あるときだけ処理する
Optional.ofNullable(scores.get("math")).ifPresent(System.out::println);

ビューであることに注意

Map<String, Integer> scores = new HashMap<>(Map.of("math", 80, "english", 70));

Set<String> keys = scores.keySet();
keys.remove("math");                  // 元の Map からも消える
System.out.println(scores);           // {english=70}

// keys.add("science");               // UnsupportedOperationException(追加はできない)

// 独立したコピーが欲しいとき
Set<String> copy = new HashSet<>(scores.keySet());
List<Integer> valueList = new ArrayList<>(scores.values());

keySet() values() entrySet()中身をコピーした新しい集合ではありません。元の Map を覗く窓なので、片方を変えるともう片方にも反映されます。

走査しながら削除する

Map<String, Integer> scores = new HashMap<>(Map.of("math", 80, "english", 60));

// 落ちる
for (String key : scores.keySet()) {
    if (scores.get(key) < 70) scores.remove(key);
}
// ConcurrentModificationException

// 対処 1: removeIf(最も簡潔)
scores.entrySet().removeIf(e -> e.getValue() < 70);
scores.keySet().removeIf(k -> k.startsWith("x"));
scores.values().removeIf(v -> v == null);

// 対処 2: Iterator
Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> e = it.next();
    if (e.getValue() < 70) it.remove();
}

// 対処 3: キーの一覧を先に固定する
for (String key : new ArrayList<>(scores.keySet())) {
    if (scores.get(key) < 70) scores.remove(key);
}

値を書き換える

// entrySet を回しながら setValue するのは許されている
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    e.setValue(e.getValue() + 10);
}

// まとめて変換する
scores.replaceAll((k, v) -> v + 10);

// あるときだけ
scores.computeIfPresent("math", (k, v) -> v + 10);

キーの追加・削除はできませんが、値の書き換えは setValue() で安全に行えます。

並べ替えて取り出す

Map<String, Integer> scores = Map.of("math", 80, "english", 70, "science", 90);

// キーの昇順
new TreeMap<>(scores).forEach((k, v) -> System.out.println(k + "=" + v));

// 値の降順(上位 2 件)
scores.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .limit(2)
    .forEach(e -> System.out.println(e.getKey() + "=" + e.getValue()));

// 並べ替えた結果を Map で受け取る(順序を保つため LinkedHashMap を指定する)
Map<String, Integer> sorted = scores.entrySet().stream()
    .sorted(Map.Entry.comparingByKey())
    .collect(Collectors.toMap(
        Map.Entry::getKey, Map.Entry::getValue,
        (a, b) -> a, LinkedHashMap::new));

Collectors.toMap() は既定で HashMap を作るため、せっかく並べ替えても順序が失われます。第 4 引数に LinkedHashMap::new を渡してください。

Stream で加工する

// 条件で絞る
Map<String, Integer> high = scores.entrySet().stream()
    .filter(e -> e.getValue() >= 80)
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// 値を変換する
Map<String, String> labels = scores.entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue() + " 点"));

// 集計
int total = scores.values().stream().mapToInt(Integer::intValue).sum();
OptionalInt max = scores.values().stream().mapToInt(Integer::intValue).max();
double avg = scores.values().stream().mapToInt(Integer::intValue).average().orElse(0);

// キーを連結する
String keys = String.join(", ", scores.keySet());

値からキーを探す

// 全走査するしかない(O(n))
Optional<String> key = scores.entrySet().stream()
    .filter(e -> e.getValue() == 80)
    .map(Map.Entry::getKey)
    .findFirst();

// 該当がすべて欲しいとき
List<String> keys = scores.entrySet().stream()
    .filter(e -> e.getValue() >= 80)
    .map(Map.Entry::getKey)
    .toList();

// 頻繁に引くなら逆引きの Map を作っておく
Map<Integer, String> reverse = scores.entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey,
                              (a, b) -> a));   // 値が重複したときの解決が必須

値が重複していると toMap()IllegalStateException: Duplicate key になります。第 3 引数でどちらを残すかを必ず指定してください。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ

同階層のページはありません

最近更新/作成されたページ