中級 60分 Lesson 7

配列・List・Set・Map・Deque

配列の罠・remove(int)vs(Object)・Arrays.asListとList.of違い・HashSet判定原理・TreeMapのnull禁止・Dequeスタック/キュー・Collections完全解説

Java Java Silver SE21 コレクション List Set Map Deque

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, byte0
float, double0.0
booleanfalse
char'\u0000'(null 文字)
参照型(String, クラス, 配列など)null

試験の罠: String[] names = new String[3]names[0]nullnames[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 を使う

オートボクシング により、intInteger は自動変換される:

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 インターフェースの主な実装は ArrayListLinkedList

ArrayList vs LinkedList

操作ArrayListLinkedList
get(i) ランダムアクセス速い(O(1))遅い(O(n)、先頭から辿る)
末尾への add速い(O(1) 償却)速い(O(1))
先頭・途中への挿入削除遅い(後ろをずらす)先頭は速い(O(1))、途中は遅い
メモリ少ない多い(ノードにポインタ付き)

結論: 迷ったら ArrayListLinkedList は「先頭・末尾への頻繁な追加削除」に特化した場合のみ。試験では「どちらを使うべきか」という問題より、それぞれの特性を問う問題が出る。

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.asListList.ofnew 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 が「同じ要素かどうか」を判定する手順:

  1. hashCode() が等しいか を確認(違えば別物)
  2. 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(バグ!)

修正: equalshashCode を正しくオーバーライドする。

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(正常)

hashCodeequals の契約:

  • equalstrue なら hashCode必ず同じ値を返さなければならない
  • hashCode が同じでも equalsfalse のことはある(ハッシュ衝突)

試験口コミ: 「自作クラスを 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);  // 比較ロジックを外から渡す

また、TreeSetTreeMapnull を格納できない(比較時に 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.Collectionsstatic メソッドの集合。コレクション操作の共通ツール箱。

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)
頻繁に先頭・末尾に追加削除したいLinkedListDeque として)両端操作 O(1)
重複を排除したい(順序不問)HashSet最速。順序は保証しない
重複排除 + 挿入順を保ちたいLinkedHashSet挿入順を保持
重複排除 + ソート済みで管理したいTreeSet常に自然順序でソート
キーで値を引きたい(順序不問)HashMap最速。null キー 1 つ許可
Map + 挿入順を保ちたいLinkedHashMapキーの挿入順で反復できる
Map + キーをソートしたいTreeMapキーの自然順序で反復。null キー不可
後入れ先出し(スタック)ArrayDequepush/popStack クラスは非推奨
先入れ先出し(キュー)ArrayDequeoffer/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]

  1. list.remove(2) → インデックス 2 の要素(15)を削除 → [5, 10, 10, 20]
  2. list.remove(Integer.valueOf(10)) → 値 10 の最初の出現(インデックス 1)を削除 → [5, 10, 20]

問題 2: Set の順序

次の3つの Set"C", "A", "B", "A" を順番に追加したときの各 SettoString() の出力を答えよ。

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() は変更不可ビューを返す(元リストへの変更は反映される)