目次
要約
- 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:ArrayList
と LinkedList
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
では、要素(キー)のequals
とhashCode
の整合 が必須。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
-
ArrayDeque
はLinkedList
より一般に高速。 -
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.remove
/removeIf
/別収集後にまとめて変更。- 等値性の契約:
equals
を実装したらhashCode
も整合 。HashSet
/HashMap
で必須。 - 可変キー禁止:
HashMap
やTreeMap
のキーを後から変更しない(探索不能になる)。 - 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.removeIf
/Map.merge
/computeIfAbsent
/ArrayDeque
の組み合わせで、典型処理が簡潔になります。
ベストプラクティス要点
- デフォルトは
ArrayList
/HashMap
/LinkedHashMap
。順序や範囲検索が必要な場合のみTree*
を選択。 - 不変化(
List.of
/copyOf
)を基本に、外部公開はディフェンシブコピー。 - 反復中の変更は
Iterator.remove
/removeIf
を使用(CME回避)。 equals
/hashCode
の契約を順守。可変キーは使わない。- 並行用途は
ConcurrentHashMap
など専用クラスを選択し、同期ラッパーの多用は避ける。