SJ blog
Java
Z

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Javaコレクションフレームワーク完全ガイド — 使い分けと内部実装

List/Set/Map/Queueの使い分け、ArrayList vs LinkedList、HashMap vs TreeMapの性能差、スレッドセーフなコレクションを解説します。

一言結論

迷ったらArrayListとHashMapを選ぶのが正解で、順序保証が必要ならLinkedHashMap、ソート済みが必要ならTreeMap、スレッドセーフが必要ならConcurrentHashMapと用途に応じて使い分けることが設計の要点だ。

コレクションの全体像

Collection
├── List(順序あり、重複あり)
│   ├── ArrayList
│   ├── LinkedList
│   └── CopyOnWriteArrayList(スレッドセーフ)
├── Set(重複なし)
│   ├── HashSet(順序なし)
│   ├── LinkedHashSet(挿入順)
│   └── TreeSet(ソート順)
└── Queue / Deque
    ├── LinkedList
    ├── ArrayDeque
    └── PriorityQueue

Map(キーと値のペア)
├── HashMap(順序なし)
├── LinkedHashMap(挿入順)
├── TreeMap(キーのソート順)
└── ConcurrentHashMap(スレッドセーフ)

List の使い分け

ArrayList

内部が配列。ランダムアクセス(O(1)) が速い。末尾への追加は平均 O(1)。中間の挿入・削除は O(n)。

List<String> list = new ArrayList<>();
list.add("a");
list.get(0);  // O(1)

LinkedList

内部が双方向連結リスト。先頭/末尾への挿入・削除 が O(1)。ランダムアクセスは O(n)。

LinkedList<String> list = new LinkedList<>();
list.addFirst("first");
list.addLast("last");
list.pollFirst();  // 先頭を取り出して削除

選択基準:

  • 末尾追加と読み取りが主 → ArrayList
  • 先頭/末尾への頻繁な追加削除 → LinkedList
  • 迷ったら ArrayList

Set の使い分け

// HashSet: 最も高速(O(1))、順序なし
Set<String> set = new HashSet<>();
set.add("apple");
set.contains("apple");  // O(1)

// LinkedHashSet: 挿入順を維持
Set<String> set = new LinkedHashSet<>();

// TreeSet: 自然順(またはComparator)でソート
Set<String> set = new TreeSet<>();
// → イテレートするとアルファベット順に

Map の使い分け

// HashMap: 最も高速(O(1))、順序なし
Map<String, Integer> map = new HashMap<>();
map.put("key", 1);
map.get("key");  // O(1)

// LinkedHashMap: 挿入順を維持
Map<String, Integer> map = new LinkedHashMap<>();

// TreeMap: キーのソート順
Map<String, Integer> map = new TreeMap<>();
map.firstKey();  // 最小のキー
map.lastKey();   // 最大のキー

Map の便利メソッド(Java 8以降)

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

// getOrDefault
int count = wordCount.getOrDefault("hello", 0);

// putIfAbsent
wordCount.putIfAbsent("world", 0);

// compute / merge で単語カウント
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
    // キーがなければ 1、あれば +1
}

// computeIfAbsent でグルーピング
Map<String, List<String>> grouped = new HashMap<>();
for (String name : names) {
    grouped.computeIfAbsent(
        name.substring(0, 1),  // 先頭文字をキーに
        k -> new ArrayList<>()
    ).add(name);
}

// forEach
wordCount.forEach((word, cnt) ->
    System.out.println(word + ": " + cnt));

// entrySet でループ
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

変更不可能なコレクション

// Java 9以降(推奨)
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("x", "y");
Map<String, Integer> immutableMap = Map.of("key1", 1, "key2", 2);

// Java 8以前
List<String> unmodifiable = Collections.unmodifiableList(list);

List.of() で作ったリストは null 要素を許容しません。null が必要な場合は Collections.unmodifiableList を使います。

スレッドセーフなコレクション

// ConcurrentHashMap: 高並列Map(synchronizedMapより高速)
Map<String, Integer> map = new ConcurrentHashMap<>();

// CopyOnWriteArrayList: 読み取りが多い場合
List<String> list = new CopyOnWriteArrayList<>();

// Collections.synchronizedList: 既存Listをスレッドセーフに
List<String> list = Collections.synchronizedList(new ArrayList<>());

Queue / Deque

// キュー(FIFO)
Queue<String> queue = new LinkedList<>();
queue.offer("first");
queue.offer("second");
queue.poll();   // "first" を取り出して削除
queue.peek();   // "second" を見るだけ

// 優先度付きキュー
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
pq.poll();  // 1(最小値)

// スタック(LIFO)として使う Deque
Deque<String> stack = new ArrayDeque<>();
stack.push("a");
stack.push("b");
stack.pop();   // "b"

性能まとめ

操作ArrayListLinkedListHashSetTreeSet
追加(末尾)O(1)O(1)O(1)O(log n)
追加(先頭)O(n)O(1)--
取得(index)O(1)O(n)--
検索O(n)O(n)O(1)O(log n)
削除O(n)O(1)*O(1)O(log n)

まとめ

  • インデックスアクセスが多いArrayList
  • 重複を防ぎたいHashSet / LinkedHashSet
  • キーで検索HashMap
  • ソート済みが必要TreeSet / TreeMap
  • マルチスレッドConcurrentHashMap
  • 変更不可List.of() / Map.of()