配列・List・Set・Map・Deque
配列の罠・remove(int)vs(Object)・Arrays.asListとList.of違い・HashSet判定原理・TreeMapのnull禁止・Dequeスタック/キュー・Collections完全解説
Chapter 07 ─ 配列・List・Set・Map・Deque
Silver 試験のコレクション問題は「どの実装クラスが正しいか」「null を入れられるか」「remove(int) vs remove(Object)」「各クラスの順序保証」が頻出。試験コミュニティでは「
List<Integer>のremove(1)が値じゃなくインデックスで消えた」というトラップに引っかかった声が多い。
7-1. 配列 ─「型・長さ固定の箱の列」
現実の比喩: 配列はホテルの客室のようなもの。部屋数は決まっていて(長さ固定)、各部屋は同じタイプ(型固定)。arr[0] は 101 号室、arr[1] は 102 号室に対応する。一度建てたら部屋数は変えられない。
配列は「参照型」
配列変数が保持するのは、配列本体への参照(メモリアドレス)。代入すると同じ配列を指す別の変数が生まれる。
int[] a = {1, 2, 3};
int[] b = a; // b は a と同じ配列を参照(コピーではない!)
b[0] = 99; // b を通じて配列を変更
System.out.println(a[0]); // 99(a も同じ配列を見ているので影響を受ける)
配列の中身をコピーしたい場合:
int[] src = {1, 2, 3};
int[] copy = Arrays.copyOf(src, src.length); // 新しい配列に値をコピー
copy[0] = 99;
System.out.println(src[0]); // 1(src は変わらない)
配列の宣言・生成・初期値
// 1. 長さ指定で生成(初期値で埋まる)
int[] nums = new int[5]; // [0, 0, 0, 0, 0]
boolean[] flags = new boolean[3]; // [false, false, false]
String[] names = new String[2]; // [null, null]
// 2. 初期化リスト(リテラル、new は省略可)
int[] nums2 = {10, 20, 30}; // 型推論で長さ3
int[] nums3 = new int[]{1, 2, 3}; // 同じ(メソッド引数などではこちら)
// 長さはフィールド .length で取得(メソッドではない!)
System.out.println(nums.length); // 5
// System.out.println(nums.length()); // コンパイルエラー!
| 要素の型 | 初期値 |
|---|---|
int, long, short, byte | 0 |
float, double | 0.0 |
boolean | false |
char | '\u0000'(null 文字) |
参照型(String, クラス, 配列など) | null |
試験の罠:
String[] names = new String[3]のnames[0]はnull。names[0].length()を呼ぶと NPE。
多次元配列とジャグ配列
// 通常の2次元配列(3行4列)
int[][] matrix = new int[3][4];
// ジャグ配列(各行の長さが異なる)
int[][] jagged = new int[3][]; // 行数だけ指定(列は未割り当て)
jagged[0] = new int[2]; // 1行目: 2要素
jagged[1] = new int[5]; // 2行目: 5要素
jagged[2] = new int[1]; // 3行目: 1要素
// jagged.length → 3(行数)
// jagged[1].length → 5(2行目の列数)
// jagged[3] を参照すると ArrayIndexOutOfBoundsException
// jagged[0] を参照した後、jagged[0][5] を参照しても AIOOBE
Arrays クラスの主要メソッド
int[] arr = {5, 3, 1, 4, 2};
// ソート(元の配列を変更する in-place ソート)
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // "[1, 2, 3, 4, 5]"
// 二分探索(ソート済みが前提!)
int idx = Arrays.binarySearch(arr, 3); // 2(見つかったインデックス)
int missing = Arrays.binarySearch(arr, 6); // 負の値(-(挿入位置)-1)
// コピー
int[] copy1 = Arrays.copyOf(arr, 3); // [1, 2, 3](先頭3要素)
int[] copy2 = Arrays.copyOfRange(arr, 1, 4); // [2, 3, 4]([1]〜[3])
// 全埋め
Arrays.fill(arr, 0); // [0, 0, 0, 0, 0]
// 内容比較(== は参照比較なので false)
int[] x = {1, 2, 3};
int[] y = {1, 2, 3};
System.out.println(Arrays.equals(x, y)); // true(内容が等しい)
System.out.println(x == y); // false(別オブジェクト)
System.arraycopy ─ 部分コピー:
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
// System.arraycopy(コピー元配列, 元の開始インデックス, コピー先配列, 先の開始インデックス, コピーする要素数)
System.arraycopy(src, 1, dest, 0, 3);
// src[1], src[2], src[3] を dest[0], dest[1], dest[2] にコピー
System.out.println(Arrays.toString(dest)); // [2, 3, 4, 0, 0]
7-2. ジェネリクス(Generics)─ 型安全なコレクション
コレクションに格納できる型をコンパイル時に指定する仕組み。「このリストには String しか入れられない」と宣言することで、取り出す時のキャストが不要になり、型ミスをコンパイル時に検出できる。
// ジェネリクスなし(古いスタイル ── 危険)
List rawList = new ArrayList();
rawList.add("hello");
rawList.add(42);
String s = (String) rawList.get(1); // 実行時 ClassCastException!
// ジェネリクスあり(現在のスタイル ── 安全)
List<String> strList = new ArrayList<>();
strList.add("hello");
// strList.add(42); // コンパイルエラー(型ミスを事前に検出)
String s2 = strList.get(0); // キャスト不要
プリミティブ型は使えない(ラッパー型を使う):
List<int> ng = new ArrayList<>(); // コンパイルエラー
List<Integer> ok = new ArrayList<>(); // ラッパー型 Integer を使う
オートボクシング により、int ↔ Integer は自動変換される:
List<Integer> list = new ArrayList<>();
list.add(42); // int 42 → Integer 42(自動ボクシング)
int n = list.get(0); // Integer 42 → int 42(自動アンボクシング)
落とし穴: オートアンボクシング時に値が
nullなら NullPointerException が発生する。
List<Integer> list = new ArrayList<>();
list.add(null);
int n = list.get(0); // NullPointerException(null を int にアンボクシング)
型消去(Type Erasure): ジェネリクスの型情報はコンパイル後の .class には残らない。実行時には List<String> も List<Integer> も単なる List。
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true(実行時は同じ型)
// a instanceof List<String> // コンパイルエラー(実行時に型情報なし)
7-3. List ─「順序あり・重複あり・インデックスアクセス」
現実の比喩: 買い物リスト。同じものを複数書けるし、書いた順番通りに管理できる。
List インターフェースの主な実装は ArrayList と LinkedList。
ArrayList vs LinkedList
| 操作 | ArrayList | LinkedList |
|---|---|---|
get(i) ランダムアクセス | 速い(O(1)) | 遅い(O(n)、先頭から辿る) |
末尾への add | 速い(O(1) 償却) | 速い(O(1)) |
| 先頭・途中への挿入削除 | 遅い(後ろをずらす) | 先頭は速い(O(1))、途中は遅い |
| メモリ | 少ない | 多い(ノードにポインタ付き) |
結論: 迷ったら ArrayList。LinkedList は「先頭・末尾への頻繁な追加削除」に特化した場合のみ。試験では「どちらを使うべきか」という問題より、それぞれの特性を問う問題が出る。
List の主要メソッド
List<String> list = new ArrayList<>();
// ── 追加 ──
list.add("A"); // 末尾に追加
list.add("B");
list.add("C");
list.add(1, "X"); // インデックス1に挿入 → [A, X, B, C]
// ── 取得 ──
String s = list.get(2); // "B"
int size = list.size(); // 4
boolean empty = list.isEmpty(); // false
// ── 更新 ──
list.set(0, "Z"); // [Z, X, B, C]
// ── 検索 ──
int idx = list.indexOf("B"); // 2(最初の出現インデックス。なければ -1)
int last = list.lastIndexOf("B"); // 2(最後の出現インデックス)
boolean has = list.contains("X"); // true
// ── 削除 ──(ここが試験の最大罠!)
list.remove(0); // インデックス0を削除("Z") → [X, B, C]
list.remove("B"); // 値 "B" を削除(最初の1つ) → [X, C]
remove(int) vs remove(Object) ─ 試験最頻出の罠
List<Integer> nums = new ArrayList<>(Arrays.asList(10, 20, 30, 20));
// remove(int): インデックス指定(int 型引数)
nums.remove(1);
// → インデックス1の要素(20)を削除 → [10, 30, 20]
// remove(Integer): 値指定(Object 型引数)
nums.remove(Integer.valueOf(30));
// → 値 30 を削除(最初の出現) → [10, 20]
// 罠: remove(20) と書くと……
// → int 20 はインデックス指定になる → インデックス 20 は存在しない → IndexOutOfBoundsException!
List<Integer> で「値を削除」したいときは必ず Integer.valueOf(値) を使う。
試験口コミ: 「
list.remove(2)がインデックスで消えた。値 2 を消したかっただけなのに」という嘆きが頻繁に見られる。
Arrays.asList() vs List.of() ─ 不変具合の違い
// Arrays.asList: 固定サイズ(要素の set は可、add/remove は不可)
List<String> fixed = Arrays.asList("A", "B", "C");
fixed.set(0, "Z"); // OK(要素の更新は可能)
// fixed.add("D"); // UnsupportedOperationException
// fixed.remove(0); // UnsupportedOperationException
// null は格納可能
// List.of: 完全不変(set/add/remove すべて不可)
List<String> immutable = List.of("A", "B", "C");
// immutable.set(0, "Z"); // UnsupportedOperationException
// immutable.add("D"); // UnsupportedOperationException
// List.of(null); // NullPointerException(null 不可)
// 可変リストにしたい場合: new ArrayList<> でラップ
List<String> mutable = new ArrayList<>(Arrays.asList("A", "B", "C"));
mutable.add("D"); // OK
Arrays.asList | List.of | new ArrayList<> | |
|---|---|---|---|
set(更新) | ○ | ✗ | ○ |
add / remove | ✗ | ✗ | ○ |
null 格納 | ○ | ✗ | ○ |
| 用途 | 初期化用 | 定数リスト | 可変リスト |
イテレーション中の要素削除 ─ ConcurrentModificationException
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "B", "D"));
// NG: 拡張 for 文でイテレーション中に remove するとエラー
// for (String s : list) {
// if (s.equals("B")) list.remove(s); // ConcurrentModificationException
// }
// OK: Iterator を使う
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("B")) {
it.remove(); // Iterator の remove を使う(安全)
}
}
System.out.println(list); // [A, C, D]
// OK: removeIf(Java 8+)も安全
list.removeIf(s -> s.equals("C"));
System.out.println(list); // [A, D]
7-4. Set ─「順序なし・重複なし」
現実の比喩: 参加者名簿。同じ人は 1 回しか載れないし、並び順は気にしない(名簿 = Set)。
3 つの主な実装
| 実装クラス | 順序 | null 格納 | 性能 | 使いどき |
|---|---|---|---|---|
HashSet | なし(不定) | 1 つ許可 | 最速 | 単純な重複排除 |
LinkedHashSet | 挿入順を保持 | 1 つ許可 | 中間 | 挿入順を保ちたい |
TreeSet | 自然順序(昇順)でソート | 不可 | やや遅い | ソート済みセットが必要 |
Set<String> hashSet = new HashSet<>();
Set<String> linkedSet = new LinkedHashSet<>();
Set<String> treeSet = new TreeSet<>();
for (Set<String> set : List.of(hashSet, linkedSet, treeSet)) {
set.add("banana");
set.add("apple");
set.add("cherry");
set.add("apple"); // 重複 → 無視(add は false を返す)
System.out.println(set);
}
// HashSet: [cherry, apple, banana](順序不定)
// LinkedHashSet:[banana, apple, cherry](挿入順)
// TreeSet: [apple, banana, cherry](アルファベット昇順)
HashSet の重複判定 ─「hashCode + equals の 2 段階チェック」
HashSet が「同じ要素かどうか」を判定する手順:
hashCode()が等しいか を確認(違えば別物)equals()がtrueか を確認(両方 true なら重複)
class Person {
String name;
Person(String name) { this.name = name; }
// equals と hashCode をオーバーライドしていない(Object のデフォルト使用)
}
Set<Person> set = new HashSet<>();
set.add(new Person("Alice"));
set.add(new Person("Alice")); // 別オブジェクト → hashCode が違う → 重複とみなされない!
System.out.println(set.size()); // 2(バグ!)
修正: equals と hashCode を正しくオーバーライドする。
class Person {
String name;
Person(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (!(o instanceof Person p)) return false;
return Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name); // name が同じなら同じ hashCode を返す
}
}
Set<Person> set = new HashSet<>();
set.add(new Person("Alice"));
set.add(new Person("Alice")); // equals も hashCode も同じ → 重複とみなされる
System.out.println(set.size()); // 1(正常)
hashCode と equals の契約:
equalsがtrueならhashCodeは必ず同じ値を返さなければならないhashCodeが同じでもequalsがfalseのことはある(ハッシュ衝突)
試験口コミ: 「自作クラスを
HashSetに入れても重複が消えなくてハマった。equalsだけオーバーライドしてhashCodeを忘れていた」という声が多い。@Override equalsを書いたら@Override hashCodeもセットで書くのが鉄則。
TreeSet の注意 ─ Comparable が必要
TreeSet は要素を自然順序でソートするために Comparable<T> の実装が必要。
// String, Integer など標準クラスは Comparable 実装済み → OK
TreeSet<String> ts = new TreeSet<>();
ts.add("cherry");
ts.add("apple");
ts.add("banana");
System.out.println(ts); // [apple, banana, cherry]
System.out.println(ts.first()); // "apple"(最小値)
System.out.println(ts.last()); // "cherry"(最大値)
// Comparable 未実装のクラスは実行時 ClassCastException
class Foo {}
TreeSet<Foo> fooSet = new TreeSet<>();
fooSet.add(new Foo()); // ClassCastException(Foo は Comparable を実装していない)
// 代替: Comparator を渡す
TreeSet<Foo> fooSet2 = new TreeSet<>((a, b) -> 0); // 比較ロジックを外から渡す
また、TreeSet と TreeMap は null を格納できない(比較時に NPE が発生するため)。
TreeSet<String> ts = new TreeSet<>();
// ts.add(null); // NullPointerException
集合演算
Set<Integer> a = new HashSet<>(Arrays.asList(1, 2, 3, 4));
Set<Integer> b = new HashSet<>(Arrays.asList(3, 4, 5, 6));
Set<Integer> union = new HashSet<>(a);
union.addAll(b); // 和集合: {1, 2, 3, 4, 5, 6}
Set<Integer> intersect = new HashSet<>(a);
intersect.retainAll(b); // 積集合(共通部分): {3, 4}
Set<Integer> diff = new HashSet<>(a);
diff.removeAll(b); // 差集合(a から b を除いたもの): {1, 2}
7-5. Map ─「キーと値のペア」
現実の比喩: 辞書。「単語(キー)」→「意味(値)」の対応表。同じ単語は 1 つしか載せられない(キー重複不可)。
3 つの主な実装
| 実装クラス | キーの順序 | null キー | null 値 | 用途 |
|---|---|---|---|---|
HashMap | なし(不定) | 1 つ許可 | 許可 | 最速・最一般的 |
LinkedHashMap | 挿入順を保持 | 1 つ許可 | 許可 | 挿入順を保ちたい |
TreeMap | キーの自然順序(昇順) | 不可 | 許可 | ソート済みマップが必要 |
Map<String, Integer> map = new HashMap<>();
// ── 追加・更新 ──
map.put("Alice", 90);
map.put("Bob", 75);
map.put("Alice", 95); // キー "Alice" の値を上書き
System.out.println(map); // {Alice=95, Bob=75}(順序不定)
// ── 取得 ──
int score = map.get("Alice"); // 95
Integer val = map.get("Unknown"); // null(キーが存在しない場合)
int def = map.getOrDefault("Unknown", 0); // 0(キーなしのデフォルト値)
// ── 判定 ──
map.containsKey("Bob"); // true
map.containsValue(75); // true
map.size(); // 2
// ── 削除 ──
map.remove("Bob"); // キー "Bob" を削除
map.remove("Alice", 90); // キーが "Alice" かつ値が 90 の場合のみ削除(条件付き)
// ── null キー(HashMap のみ) ──
map.put(null, -1); // null キーは 1 つだけ許可
System.out.println(map.get(null)); // -1
イテレーション
Map<String, Integer> scores = new LinkedHashMap<>(); // 挿入順保持
scores.put("Alice", 90);
scores.put("Bob", 75);
scores.put("Carol", 88);
// entrySet: キーと値のペアをセットで取得
for (Map.Entry<String, Integer> e : scores.entrySet()) {
System.out.println(e.getKey() + " → " + e.getValue());
}
// keySet: キーだけ
for (String key : scores.keySet()) {
System.out.println(key + ": " + scores.get(key));
}
// values: 値だけ
for (int v : scores.values()) {
System.out.println(v);
}
// forEach(Java 8+)
scores.forEach((k, v) -> System.out.println(k + ": " + v));
便利メソッド
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
// putIfAbsent: キーがなければ追加(あれば何もしない)
scores.putIfAbsent("Alice", 0); // Alice は既存 → 変更なし(90 のまま)
scores.putIfAbsent("Dave", 70); // Dave は新規 → 追加
// getOrDefault: 典型的な集計パターン
Map<String, Integer> freq = new HashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "apple"};
for (String w : words) {
freq.put(w, freq.getOrDefault(w, 0) + 1);
}
System.out.println(freq); // {apple=3, banana=1, cherry=1}
// compute: キーの現在値を関数で更新
scores.compute("Alice", (k, v) -> v == null ? 1 : v + 10); // 90 → 100
// merge: 値をマージ(存在しなければ新値、あれば関数でマージ)
scores.merge("Alice", 5, Integer::sum); // 100 + 5 = 105
scores.merge("Eve", 80, Integer::sum); // Eve は新規 → 80
// computeIfAbsent: キーがない場合のみ計算して追加
scores.computeIfAbsent("Frank", k -> k.length() * 10); // "Frank" の長さ5 × 10 = 50
不変 Map(Java 9+)
// Map.of: 最大 10 エントリまで(以降は Map.ofEntries)
Map<String, Integer> immutable = Map.of("A", 1, "B", 2, "C", 3);
// immutable.put("D", 4); // UnsupportedOperationException
// null キー・null 値は不可(NullPointerException)
// 順序は保証されない
// Map.ofEntries: エントリ数に制限なし
Map<String, Integer> large = Map.ofEntries(
Map.entry("Alice", 90),
Map.entry("Bob", 75)
// ...
);
7-6. Deque ─ 両端キュー(スタック + キューの万能型)
現実の比喩: 両側に出入口のある廊下。前からも後ろからも入れる・出られる。
Deque(デック)は先頭(First)と末尾(Last)の両方に対して追加・削除ができる。キュー(FIFO)にもスタック(LIFO)にもなれる。
ArrayDeque vs LinkedList(Deque として使う場合)
ArrayDeque: 推奨(メモリ効率が良い、null 禁止でバグを防ぎやすい)
LinkedList: Deque も実装しているが、null を許容するためバグのリスクあり
Stack クラス: 非推奨(Java 1.0 の古いクラス)→ ArrayDeque を使う
メソッド早見表
| 操作 | 例外を投げる | null/false を返す |
|---|---|---|
| 先頭に追加 | addFirst(e) | offerFirst(e) |
| 末尾に追加 | addLast(e) | offerLast(e) |
| 先頭を削除して返す | removeFirst() | pollFirst() |
| 末尾を削除して返す | removeLast() | pollLast() |
| 先頭を見るだけ(削除なし) | getFirst() | peekFirst() |
| 末尾を見るだけ(削除なし) | getLast() | peekLast() |
スタック専用メソッド(Deque のスタック的使い方):
push(e)=addFirst(e)(先頭に積む)pop()=removeFirst()(先頭から取る)peek()=peekFirst()(先頭を見るだけ)
キューとして使う(FIFO: 先入れ先出し)
Deque<String> queue = new ArrayDeque<>();
// 末尾に追加
queue.offer("Alice");
queue.offer("Bob");
queue.offer("Carol");
// 中身: [Alice, Bob, Carol](Alice が先頭)
System.out.println(queue.poll()); // "Alice"(先頭を削除して返す)
System.out.println(queue.peek()); // "Bob"(先頭を見るだけ)
System.out.println(queue); // [Bob, Carol]
スタックとして使う(LIFO: 後入れ先出し)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // 1 を積む → [1]
stack.push(2); // 2 を積む → [2, 1]
stack.push(3); // 3 を積む → [3, 2, 1]
System.out.println(stack.pop()); // 3(最後に積んだものが先に出る)
System.out.println(stack.peek()); // 2(次に出るもの)
System.out.println(stack); // [2, 1]
7-7. Collections ユーティリティ
java.util.Collections は static メソッドの集合。コレクション操作の共通ツール箱。
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6));
// ── ソート・並び替え ──
Collections.sort(list); // 自然順序で昇順
Collections.sort(list, Collections.reverseOrder()); // 降順
Collections.reverse(list); // 要素の並び順を逆にする(ソートではない)
Collections.shuffle(list); // ランダム並び替え
// ── 探索・統計 ──
int max = Collections.max(list); // 最大値
int min = Collections.min(list); // 最小値
int freq = Collections.frequency(list, 1); // 値 1 の出現回数
// ── 不変ビュー(変更を試みると UnsupportedOperationException) ──
List<Integer> unmod = Collections.unmodifiableList(list);
// unmod.add(10); // UnsupportedOperationException
// ── 単一要素・空の不変コレクション ──
List<String> single = Collections.singletonList("only");
Set<String> single2 = Collections.singleton("only");
List<String> empty = Collections.emptyList();
Set<String> empty2 = Collections.emptySet();
Map<String, Integer> emptyMap = Collections.emptyMap();
// ── 同じ値を n 個持つリスト ──
List<String> filled = Collections.nCopies(3, "hello");
// ["hello", "hello", "hello"](不変)
// ── 2つのコレクションに共通要素があるかチェック ──
boolean noCommon = Collections.disjoint(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
);
System.out.println(noCommon); // true(共通なし)
7-8. コレクション選択のまとめ
「どれを使えばよいか分からない」という場面のための早見表。選択の基準は「順序が必要か」「重複を許すか」「キーで引くか」「出し入れの方向はどちら側か」の 4 軸。
| 目的・要件 | 使うクラス | 理由 |
|---|---|---|
| 順番があってインデックスで取りたい | ArrayList | ランダムアクセス O(1) |
| 頻繁に先頭・末尾に追加削除したい | LinkedList(Deque として) | 両端操作 O(1) |
| 重複を排除したい(順序不問) | HashSet | 最速。順序は保証しない |
| 重複排除 + 挿入順を保ちたい | LinkedHashSet | 挿入順を保持 |
| 重複排除 + ソート済みで管理したい | TreeSet | 常に自然順序でソート |
| キーで値を引きたい(順序不問) | HashMap | 最速。null キー 1 つ許可 |
| Map + 挿入順を保ちたい | LinkedHashMap | キーの挿入順で反復できる |
| Map + キーをソートしたい | TreeMap | キーの自然順序で反復。null キー不可 |
| 後入れ先出し(スタック) | ArrayDeque(push/pop) | Stack クラスは非推奨 |
| 先入れ先出し(キュー) | ArrayDeque(offer/poll) | 軽量・null 禁止でバグ検出しやすい |
迷ったときのデフォルト選択:
- 単なるリスト →
ArrayList - 重複なしのコレクション →
HashSet - キーバリュー →
HashMap - スタック/キュー →
ArrayDeque
✏️ 練習問題
問題 1: remove の罠
List<Integer> list = new ArrayList<>(Arrays.asList(5, 10, 15, 10, 20));
list.remove(2);
list.remove(Integer.valueOf(10));
System.out.println(list);
答え
[5, 10, 20]
list.remove(2)→ インデックス 2 の要素(15)を削除 →[5, 10, 10, 20]list.remove(Integer.valueOf(10))→ 値 10 の最初の出現(インデックス 1)を削除 →[5, 10, 20]
問題 2: Set の順序
次の3つの Set に "C", "A", "B", "A" を順番に追加したときの各 Set の toString() の出力を答えよ。
Set<String> h = new HashSet<>();
Set<String> l = new LinkedHashSet<>();
Set<String> t = new TreeSet<>();
// 全部に: h.add("C"); h.add("A"); h.add("B"); h.add("A"); と同じ操作をする
答え
HashSet: 順序不定(例:[A, B, C]の場合もあるが保証なし)LinkedHashSet:[C, A, B](挿入順。“A” の 2 回目は重複で無視)TreeSet:[A, B, C](自然順序 = アルファベット昇順)
問題 3: null の可否
| 操作 | コンパイルエラー | 実行時例外 | 正常 |
|---|---|---|---|
new ArrayList<>().add(null) | |||
List.of(null) | |||
new TreeSet<>().add(null) | |||
new HashMap<>().put(null, 1) | |||
new TreeMap<>().put(null, 1) |
答え
| 操作 | 結果 |
|---|---|
new ArrayList<>().add(null) | 正常(ArrayList は null OK) |
List.of(null) | 実行時例外(NullPointerException) |
new TreeSet<>().add(null) | 実行時例外(NullPointerException、比較不能) |
new HashMap<>().put(null, 1) | 正常(HashMap は null キー 1 つ許可) |
new TreeMap<>().put(null, 1) | 実行時例外(NullPointerException、null キー比較不能) |
Chapter 07 チェックリスト
-
array.lengthはフィールド(.length()メソッドではない) - 配列は参照型 → 代入は参照のコピー(内容コピーは
Arrays.copyOf) - 配列の初期値: 数値 0, boolean false, 参照型 null
-
Arrays.sort()は in-place ソート(元の配列を変更) -
Arrays.equals()は内容比較、==は参照比較 - ジェネリクスのパラメータにプリミティブ型は使えない(ラッパー型を使う)
- null のオートアンボクシングは NullPointerException
-
ArrayListはランダムアクセス速い、LinkedListは両端挿入削除速い -
List<Integer>のremove(int)はインデックス、remove(Integer)は値(試験最頻出の罠) -
Arrays.asList()は固定サイズ(set は可、add/remove は不可) -
List.of()は完全不変(set も不可)、null 不可 - 拡張 for 文でイテレーション中に remove すると
ConcurrentModificationException -
HashSet順序なし、LinkedHashSet挿入順、TreeSet自然順序ソート -
HashSetの重複判定 =hashCode()が同じ かつequals()が true -
TreeSet/TreeMapに null は格納できない(NullPointerException) -
TreeSet/TreeMapの要素/キーはComparable実装が必要(またはコンストラクタでComparator指定) -
HashMapは null キーを 1 つ許可、TreeMapは null キー不可 -
HashMap順序なし、LinkedHashMap挿入順、TreeMapキー昇順 -
Map.get()はキーなしで null、getOrDefault()でデフォルト値指定 -
putIfAbsent()はキーが存在しない場合のみ追加 -
Dequeはスタック(push/pop)とキュー(offer/poll)の両用 -
Stackクラスは非推奨 →ArrayDequeを使う - 複数リソースのクローズは宣言の逆順
-
Collections.unmodifiableList()は変更不可ビューを返す(元リストへの変更は反映される)