コレクションとジェネリクスの要点
内容の確認日: 2026-10-04
この分野では、コレクション・フレームワークの主要なインタフェースとクラスの使い分け、総称型 (ジェネリクス) の型の互換性、Comparator による並べ替えが問われます。API のメソッドが「何を返すか」「元のコレクションを変えるか」を正確に覚えているかが得点の分かれ目です。
出題項目は「オート・ボクシングの使用」「コレクション・フレームワークの概要と主要なコレクション・インタフェース (List, Map, Set および Deque) の使用」「総称型の使用」「Comparator インタフェースを使用したコレクションのソート」「コレクション用の便利なメソッドの使用」の 5 つです。
オートボクシングとアンボクシング
int から Integer への自動変換 (オートボクシング) には Integer.valueOf が使われます。valueOf は -128 〜 127 の値をキャッシュしているので、この範囲の値は同じオブジェクトになり、== で比べると true になります。範囲外の値は別のオブジェクトになるので、値の比較には equals を使います。
public class Main { public static void main(String[] args) { Integer a = 127, b = 127; Integer c = 128, d = 128; System.out.println(a == b); System.out.println(c == d); System.out.println(c.equals(d)); }}java Main true false true
オートボクシングが行うのは「プリミティブ型 → 対応するラッパー型」の変換だけです。拡大変換とボクシングを続けて行うことはできないので、int のリテラル 10 を Long 型の変数に代入するとコンパイルエラーです (10L なら可)。
public class Main { public static void main(String[] args) { Long n = 10; System.out.println(n); }}javac Main.java
コンパイルエラー (3 行目): int の 10 は Long に変換できない (int → long → Long の 2 段階の変換は行われない)
ラッパー型の値が null のときにアンボクシングが起きると、NullPointerException が発生します。Map の get は存在しないキーに対して null を返すので、そのまま計算に使うと危険です。getOrDefault を使うと安全です。
import java.util.HashMap;import java.util.Map;public class Main { public static void main(String[] args) { Map<String, Integer> stock = new HashMap<>(); stock.put("apple", 3); System.out.println(stock.getOrDefault("lemon", 0) + 1); int total = stock.get("lemon") + 1; System.out.println(total); }}java Main 1
実行時に例外が発生: NullPointerException
List・Set・Map・Deque の使い分け
主なインタフェースと実装クラスの特徴は次のとおりです。
・List: 順序あり・重複可。ArrayList (添字での読み取りが速い)、LinkedList
・Set: 重複なし。HashSet (順序なし)、LinkedHashSet (追加した順)、TreeSet (並べ替えた順)
・Map: キーと値の組。キーは重複なし。HashMap、LinkedHashMap、TreeMap (キーの順)
・Deque: 先頭と末尾の両方から出し入れできるキュー。ArrayDeque、LinkedList
Deque はメソッドが多く、先頭・末尾のどちらを操作するかを取り違えやすいので注意が必要です。push・pop・offerFirst・pollFirst は先頭、offer・add・offerLast・pollLast は末尾を操作します。peek は先頭の要素を取り出さずに返します。
import java.util.ArrayDeque;import java.util.Deque;public class Main { public static void main(String[] args) { Deque<Integer> dq = new ArrayDeque<>(); dq.offer(1); dq.offer(2); dq.push(0); dq.offerFirst(-1); System.out.println(dq); System.out.println(dq.pop() + " " + dq.pollLast()); System.out.println(dq); }}java Main [-1, 0, 1, 2] -1 2 [0, 1]
TreeMap (と TreeSet) には、キーの大小で検索するメソッドがあります。ceilingKey(k) は k 以上で最小のキー、floorKey(k) は k 以下で最大のキーを返します。headMap(k) は k より小さい (k を含まない) 部分、tailMap(k) は k 以上 (k を含む) 部分です。
import java.util.Map;import java.util.TreeMap;public class Main { public static void main(String[] args) { TreeMap<Integer, String> map = new TreeMap<>(Map.of(10, "a", 20, "b", 30, "c")); System.out.println(map.ceilingKey(15) + " " + map.floorKey(15)); System.out.println(map.headMap(20) + " " + map.tailMap(20)); System.out.println(map.firstEntry().getValue()); }}java Main 20 10 {10=a} {20=b, 30=c} a
変更できないリストにも 2 種類あります。Arrays.asList が返すリストは元の配列と結び付いた固定長のリストで、set による置き換えはできますが、add・remove はできません。List.of が返すリストは完全に変更できず、set も UnsupportedOperationException になります。
import java.util.Arrays;import java.util.List;public class Main { public static void main(String[] args) { List<String> fixed = Arrays.asList("a", "b"); fixed.set(0, "x"); System.out.println(fixed); List<String> immutable = List.of("a", "b"); immutable.set(0, "x"); }}java Main [x, b]
実行時に例外が発生: UnsupportedOperationException
総称型とワイルドカード
総称型は、型引数が違えば継承関係があっても互換性がありません。Integer は Number のサブクラスですが、List<Integer> は List<Number> のサブタイプではないので、代入するとコンパイルエラーです。
import java.util.ArrayList;import java.util.List;public class Main { public static void main(String[] args) { List<Integer> ints = new ArrayList<>(); List<Number> nums = ints; System.out.println(nums); }}javac Main.java
コンパイルエラー (7 行目): List<Integer> は List<Number> に代入できない
いろいろな型のリストを受け取りたいときは、ワイルドカードを使います。
・List<? extends Number>: Number かそのサブクラスのリスト。要素は Number として取り出せるが、null 以外は追加できない
・List<? super Integer>: Integer かそのスーパークラスのリスト。Integer を追加できるが、取り出した要素は Object としてしか扱えない
・List<?>: 何かの型のリスト。要素は Object として取り出せるだけ
import java.util.ArrayList;import java.util.List;public class Main { static double sum(List<? extends Number> nums) { double total = 0; for (Number n : nums) { total += n.doubleValue(); } return total; } static void addInts(List<? super Integer> dst) { dst.add(1); dst.add(2); } public static void main(String[] args) { List<Number> list = new ArrayList<>(); addInts(list); list.add(2.5); System.out.println(sum(list)); System.out.println(list); }}java Main 5.5 [1, 2, 2.5]
型パラメータには <T extends Comparable<T>> のように上限境界を付けられます。境界を付けると、メソッドの中で T の値に対して compareTo などの境界の型のメソッドを呼べます。
総称型の型引数はコンパイル後に消去されます (イレイジャ)。そのため、print(List<String>) と print(List<Integer>) のように型引数だけが違うメソッドは、実行時には同じ print(List) になってしまい、オーバーロードできません (コンパイルエラー)。
Comparator による並べ替え
Comparator.comparing(キーを取り出す関数) で、そのキーの昇順に並べる Comparator を作れます。reversed() で逆順にし、thenComparing でキーが同じときの次の基準を足せます。thenComparing の第 2 引数に Comparator を渡すと、2 つ目のキーだけを降順にすることもできます。
import java.util.ArrayList;import java.util.Comparator;import java.util.List;record Item(String name, int price) {}public class Main { public static void main(String[] args) { List<Item> items = new ArrayList<>(List.of( new Item("pen", 100), new Item("cup", 300), new Item("ink", 100))); items.sort(Comparator.comparing(Item::price) .thenComparing(Item::name, Comparator.reverseOrder())); items.forEach(i -> System.out.println(i.name() + " " + i.price())); }}java Main pen 100 ink 100 cup 300
null を含む要素を並べるときは、Comparator.nullsFirst(比較方法)・nullsLast(比較方法) で null の位置を決めます。
TreeSet や TreeMap に Comparator を渡すと、equals ではなく compare の結果が 0 になる要素を同じ要素とみなします。次のコードでは、長さで比べているので、cat と同じ長さの dog は追加されません。
import java.util.Comparator;import java.util.List;import java.util.Set;import java.util.TreeSet;public class Main { public static void main(String[] args) { Set<String> set = new TreeSet<>(Comparator.comparing(String::length)); set.addAll(List.of("cat", "dog", "bird", "ox")); System.out.println(set); }}java Main [ox, cat, bird]
コレクションの便利なメソッド
Java SE 8 以降、コレクションには処理を関数で渡すメソッドが追加されています。
・Map.merge(キー, 値, 関数): キーがなければ値を入れ、あれば既存の値と合成する。単語の出現回数を数えるのに便利
・Map.computeIfAbsent(キー, 関数): キーがないときだけ、関数の結果を入れる
・Collection.removeIf(条件): 条件に合う要素をすべて削除する
・List.replaceAll(関数): 各要素を関数の結果に置き換える
Collections.unmodifiableList は、元のリストを変更できないように包んだビューを返します。ビュー自体は変更できませんが、元のリストを変更すると、その変更はビューからも見えます (コピーではないため)。
import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Map;import java.util.TreeMap;public class Main { public static void main(String[] args) { Map<String, Integer> count = new TreeMap<>(); for (String w : "a b a c b a".split(" ")) { count.merge(w, 1, Integer::sum); } System.out.println(count); List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5)); nums.removeIf(n -> n % 2 == 0); nums.replaceAll(n -> n * 10); System.out.println(nums); List<Integer> view = Collections.unmodifiableList(nums); nums.add(70); System.out.println(view); }}java Main {a=3, b=2, c=1} [10, 30, 50] [10, 30, 50, 70]
試験でよくある引っかけ
Integerの==比較-128〜127の値はキャッシュされるので==がtrueになりますが、範囲外はfalseです。値の比較はequalsを使います。nullのアンボクシングmap.get(キー) + 1は、キーがなくnullが返るとNullPointerExceptionです。Dequeの先頭と末尾push・popは先頭、offer・addは末尾を操作します。1 つずつ状態を書いて追いましょう。Arrays.asListとList.ofArrays.asListはsetできるがaddできない固定長、List.ofはsetもできません。List<Integer>とList<Number>総称型は型引数が違えば互換性がありません。受け取るには
List<? extends Number>を使います。TreeSetの重複判定Comparatorを渡したTreeSetは、compareが0の要素を重複とみなして追加しません。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Oracle University 日本「Java SE 17 Programmer II (1Z0-826-JPN) 試験」
- The Java Tutorials「Collections」
- The Java Tutorials「Generics」
- Java SE 17 API ドキュメント「Comparator」
コード例は、試験の対象バージョンで実際にコンパイル・実行して結果を確認しています。