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"
性能まとめ
| 操作 | ArrayList | LinkedList | HashSet | TreeSet |
|---|---|---|---|---|
| 追加(末尾) | 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()