コレクション

要約

  • Javaコレクションは List / Set / Map / Queue / Deque を中心に、用途別に実装を選択します。
  • 等値性(equals/hashCode)と順序の扱い、不変化並行コレクションが実務の要点です。
  • computeIfAbsent / merge / getOrDefault などの Mapユーティリティを活用するとコードが簡潔になります。
  • 反復は for-each を基本に、必要に応じて Iteratorのremove / removeIf を使用します(CME対策)。

1. 全体像と基本方針

  • List(重複可・順序あり)/Set(重複不可)/Map(キー→値)
  • Queue/Deque(待ち行列・両端キュー)/PriorityQueue(優先度付き)
  • 配列は固定長。コレクションは可変長・豊富なAPIが利用可能。
  • ジェネリクス(例:List<String>)で型安全に扱います。

2. List:ArrayListLinkedList

import java.util.*;

List<String> names = new ArrayList<>();
names.add("Alice");          // 末尾追加(償却O(1))
names.add("Bob");
names.set(1, "Bobby");       // 置換
String first = names.get(0); // 参照O(1)
names.remove(0);             // 先頭削除はコスト高(シフト発生)

// 反復と安全な削除
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("B")) it.remove(); // Iterator.removeでCME回避
}

// ソート
names.add("Charlie");
names.add("Anna");
names.sort(Comparator.naturalOrder()); // 昇順
  • ArrayList:ランダムアクセスが速い。中間挿入・先頭削除はコスト高。
  • LinkedList:挿入・削除は局所的に有利だが、総合的にはArrayList優位の場面が多い。

3. Set:HashSet / LinkedHashSet / TreeSet

Set<String> s1 = new HashSet<>();          // 順序なし・高速
Set<String> s2 = new LinkedHashSet<>();    // 追加順を保持
Set<String> s3 = new TreeSet<>();          // 並べ替え順(自然順/Comparator)

s2.add("b"); s2.add("a"); s2.add("b"); // 重複は無視
boolean contains = s2.contains("a");

// ソート基準が必要ならTreeSet
Set<String> sorted = new TreeSet<>(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
sorted.addAll(s2);
  • HashSet/HashMapでは、要素(キー)の equalshashCodeの整合 が必須。
  • LinkedHashSetは順序保持が必要なときに有効。
  • TreeSetは自然順/Comparatorに基づく順序付き集合

4. Map:HashMap / LinkedHashMap / TreeMap

import java.util.*;

Map<String, Integer> stock = new HashMap<>();
stock.put("apple", 10);
stock.put("banana", 5);

// 取得ユーティリティ
int n = stock.getOrDefault("orange", 0);

// 生成しながら追加(頻出パターン)
List<Integer> xs = List.of(1,2,3,4,5,2,4);
Map<Integer, Integer> freq = new HashMap<>();
for (int v : xs) {
    freq.merge(v, 1, Integer::sum); // vの出現回数を集計
}

// ネスト構造の初期化
Map<String, List<String>> groups = new LinkedHashMap<>();
groups.computeIfAbsent("A", k -> new ArrayList<>()).add("Alice");
groups.computeIfAbsent("B", k -> new ArrayList<>()).add("Bob");

// 反復(順序保証が必要ならLinkedHashMap)
for (Map.Entry<String,Integer> e : stock.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}
  • HashMap:高速・順序なし。
  • LinkedHashMap挿入順(またはアクセス順)を保持。LRU的用途にも。
  • TreeMap:キー順。範囲検索(subMap/headMap/tailMapが可能。

5. Queue / Deque / PriorityQueue

import java.util.*;

Deque<String> dq = new ArrayDeque<>(); // 推奨:軽量高速
dq.offerLast("A"); dq.offerLast("B");      // キュー(FIFO)
String x = dq.pollFirst();                 // A
dq.offerFirst("Z");                        // デック:両端操作

PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小さい順(最小ヒープ)
pq.offer(5); pq.offer(2); pq.offer(9);
int min = pq.poll(); // 2
  • ArrayDequeLinkedListより一般に高速。
  • PriorityQueue は「常に最小/最大を取り出す」用途に有効(Comparator指定可)。

6. 不変・読み取り専用コレクション

import java.util.*;

// Java 9+ のリテラル
List<String> l1 = List.of("A","B","C");  // 不変、null不可
Set<String>  se = Set.of("A","B","C");
Map<String,Integer> m1 = Map.of("a",1, "b",2);

// コピーして不変化
List<String> safe = List.copyOf(l1);     // シャローコピー+不変
List<String> ro   = Collections.unmodifiableList(new ArrayList<>(l1)); // ビュー
  • of 系は null不可要素変更不可
  • ディフェンシブコピーで外部公開を安全化。
  • 不変は並行性・安全性・推論容易性の観点で推奨。

7. 並行コレクション(概要)

import java.util.concurrent.*;

// 高スループットなスレッド安全Map
ConcurrentHashMap<String, Long> cmap = new ConcurrentHashMap<>();
cmap.merge("ok", 1L, Long::sum);

// 参照中心のリスト(更新は稀)
CopyOnWriteArrayList<String> cow = new CopyOnWriteArrayList<>();
cow.add("A"); cow.add("B");

// 待ち行列
ConcurrentLinkedQueue<String> cq = new ConcurrentLinkedQueue<>();
cq.offer("job"); cq.poll();
  • ConcurrentHashMap はロック分割/非ブロッキングで高スループット。
  • CopyOnWriteArrayListは読取多・更新稀に適合(更新コスト大)。
  • Collections.synchronizedList単純ロックでスループットが落ちやすい。
  • カウンタ用途は LongAdder が有効なことがあります。

8. 反復とストリーム(さわり)

List<String> words = List.of("a","bbb","cc");
int totalLen = 0;
for (String w : words) totalLen += w.length(); // 伝統的

// ストリーム(詳細は次回「ラムダ式」で)
int sum = words.stream().mapToInt(String::length).sum();
List<String> filtered = words.stream()
        .filter(w -> w.length() >= 2)
        .sorted(Comparator.comparingInt(String::length).reversed())
        .toList();
  • for-eachは可読・デバッグ容易。
  • Streamはフィルタ・変換・集計の合成に強力(並列は次々回)。

9. つまずきやすい点(CME・等値性・可変キー)

  • ConcurrentModificationException:反復中に同一コレクションを外部から変更すると発生。Iterator.removeremoveIf/別収集後にまとめて変更。
  • 等値性の契約equalsを実装したら hashCodeも整合HashSet/HashMapで必須。
  • 可変キー禁止HashMapTreeMapキーを後から変更しない(探索不能になる)。
  • LinkedList乱用:キャッシュ局所性が悪く一般に遅い。特別な理由がない限りArrayList

10. 実用サンプル(1ファイル)

import java.util.*;
import java.util.concurrent.*;

public class CollectionsDemo {
    public static void main(String[] args) {
        // List: 追加・削除・ソート
        List<Integer> nums = new ArrayList<>(List.of(5,2,9,2,4,5,1));
        nums.removeIf(n -> n < 2);            // フィルタ
        nums.sort(Comparator.naturalOrder()); // [2,2,4,5,5,9]
        System.out.println(nums);

        // Set: 重複排除+追加順保持
        Set<Integer> uniq = new LinkedHashSet<>(nums);
        System.out.println(uniq);             // [2, 4, 5, 9]

        // Map: 頻度表をmergeで
        Map<Integer,Integer> freq = new HashMap<>();
        for (int n : nums) freq.merge(n, 1, Integer::sum);
        System.out.println(freq);             // {2=2, 4=1, 5=2, 9=1}

        // Map: ネスト構築 computeIfAbsent
        Map<String,List<Integer>> bucket = new LinkedHashMap<>();
        for (int n : nums) {
            String key = (n % 2 == 0) ? "even" : "odd";
            bucket.computeIfAbsent(key, k -> new ArrayList<>()).add(n);
        }
        System.out.println(bucket);           // {even=[2,2,4], odd=[5,5,9]}

        // Queue/Deque: 両端操作
        Deque<String> dq = new ArrayDeque<>();
        dq.offerLast("A"); dq.offerLast("B"); dq.offerFirst("Z");
        while (!dq.isEmpty()) System.out.print(dq.pollFirst()); // ZAB
        System.out.println();

        // 不変化
        List<Integer> ro = List.copyOf(nums); // 以降変更不可
        System.out.println(ro);

        // 並行Map(単体サンプル):スレッド安全な累積
        ConcurrentHashMap<String, Long> cm = new ConcurrentHashMap<>();
        for (String w : List.of("a","b","a","c","b","a")) cm.merge(w, 1L, Long::sum);
        System.out.println(cm);               // {a=3, b=2, c=1}
    }
}
  • List.removeIfMap.mergecomputeIfAbsentArrayDeque の組み合わせで、典型処理が簡潔になります。

ベストプラクティス要点

  • デフォルトは ArrayList / HashMap / LinkedHashMap。順序や範囲検索が必要な場合のみ Tree* を選択。
  • 不変化(List.of / copyOf)を基本に、外部公開はディフェンシブコピー
  • 反復中の変更は Iterator.remove / removeIf を使用(CME回避)。
  • equals/hashCode の契約を順守。可変キーは使わない
  • 並行用途は ConcurrentHashMap など専用クラスを選択し、同期ラッパーの多用は避ける