Java Gold SE 17のコレクション・ジェネリクス対策と例題

Listの変更可能性とワイルドカードの読み書きを、短いコードと例題2問で確認します。

インターフェースの契約と実装を分ける

型基本の性質確認する点
List要素に位置があり、重複を扱える要素を追加・置換できるかは生成方法にも依存する
Set重複しない要素を扱うSetという型だけでは挿入順は保証されない
Mapキーと値を対応させる同じキーへのputは対応する値を更新する
Deque両端から要素を追加・取得・削除できる空の場合に例外になる操作と、nullを返す操作を区別する

List.ofとArrays.asList:固定長と変更不可の違い

Arrays.asListは元の配列に連動する固定長リストです。要素を置き換えるsetは使えますが、サイズを増やすaddは例外になります。

String[] source = {"A", "B"};
List<String> fixed = Arrays.asList(source);
fixed.set(0, "X");
System.out.println(source[0]); // X
// fixed.add("C");
// 上の行を実行するとUnsupportedOperationException

List<String> copy = new ArrayList<>(fixed);
copy.add("C");
System.out.println(copy); // [X, B, C]

new ArrayList<>(fixed)は別のリストを作るため、要素の追加・削除ができます。一方、List.ofで作るリストは、追加・削除だけでなく置換もできず、null要素も許しません。

List<String> readOnly = List.of("A", "B");
// readOnly.set(0, "X");
// 上の行を実行するとUnsupportedOperationException

「Listなら変更できる」と型名だけで判断せず、生成方法まで確認してください。詳細はList.ofの公式仕様とArrays.asListの公式仕様で確認できます。

型変数は宣言されている場所を確認する

Box<T>のTは、フィールドやインスタンスメソッドの引数・戻り値に使えます。一方、クラスの型変数Tをstaticフィールドの型には使えず、new T()で生成することもできません。メソッド自身が別の型変数を宣言するジェネリックメソッドとは分けて考えます。

コードを読む順番は「宣言型→生成した型→追加する値→取得した値の型」です。コンパイルの可否と、実行時に操作をサポートしているかを別々に判定します。型変数の規則はJava SE 17言語仕様のジェネリッククラスを参照してください。

extendsとsuper:読み取れる型・追加できる型

List<Integer>をList<Number>へ代入することはできません。ワイルドカードを使うと、同じリストを次のように参照できます。

List<Integer> values = new ArrayList<>();
values.add(10);

List<? extends Number> reader = values;
Number value = reader.get(0); // 10
// reader.add(20); // コンパイルエラー

List<? super Integer> writer = values;
writer.add(20);
Object result = writer.get(0);
System.out.println(values); // [10, 20]

? extends Numberから取得した値はNumberとして扱えます。しかし参照先がList<Integer>かList<Double>かをこの型だけでは判断できず、Integerの値を追加できません。? super IntegerにはIntegerの値を追加できますが、取得した値を型の保証だけでIntegerへ代入することはできません。Objectとして受け取ります。

extendsへのnullの追加は型の規則では許されます。ここでの説明はnull以外の値についてです。また、型が合っていても、変更不可のリストへのaddは実行時に失敗します。詳しくはワイルドカードの言語仕様を参照してください。

例題2問:型変数とコレクションの用途

宣言がコンパイルできるか、標準インターフェースとして保証される説明かを区別して答えてください。

例題1:クラスの型変数Tを使える場所

Box<T>の実装をレビューしています。クラスの型変数Tの使い方として、コンパイルできる宣言を2つ選んでください。

class Box<T> {
    // 各選択肢の宣言をここに置く
    // readの検証時は value フィールドがあるものとする
}

2つ選択してください

  • A. T read() { return value; }
  • B. static T shared;
  • C. private T value;
  • D. T create() { return new T(); }
例題1の正解・解説を見る

正解:A・C

TはインスタンスのAPIを型付けします。

TはインスタンスのAPIを型付けします。実行時の具体的なコンストラクタを指定するものではなく、全インスタンスで共有するstaticメンバの型にもなりません。

  • A:同じクラスのインスタンスメソッドはTを戻り値に使えます。
  • B:クラスの型変数はstaticコンテキストから参照できません。
  • C:Tはインスタンスフィールドの型に使えます。
  • D:型変数をnewの対象にはできません。

確認するポイント:型変数の宣言がクラス側か、メソッド側かを確認します。

仕様を確認するための公式資料:

問題ID:jg17-b5b1dd11d344e7 · この問題の訂正を連絡する

例題2:コレクションの用途と保証

処理履歴を管理するAPIの選定です。標準インタフェースの契約に合う説明を2つ選んでください。

2つ選択してください

  • A. Dequeなら先頭と末尾の双方から取り出せる
  • B. Setなら挿入順が必ず保存される
  • C. Listなら同じイベントを重複して記録し、位置で読み出せる
  • D. Mapのキーは同じ値を複数登録できる
例題2の正解・解説を見る

正解:A・C

Listは位置を持つ列、Dequeは両端から操作する列です。

Listは位置を持つ列、Dequeは両端から操作する列です。順序保証や重複の扱いはインタフェースごとに異なります。

  • A:Dequeは両端キューです。
  • B:Set全体の契約に挿入順保証はありません。
  • C:Listは順序とインデックスを持ち、重複を許します。
  • D:同一キーへのputは対応する値を更新します。

確認するポイント:MapはCollectionのサブインタフェースではありません。

仕様を確認するための公式資料:

問題ID:jg17-9395443e4e8344 · この問題の訂正を連絡する

コレクションとジェネリクスの55問で復習する

コレクションとジェネリクスの演習では、10問から取り組めます。出題数は変更できます。55問は本サイトの収録数です。公式試験の出題比率を示すものではありません。

誤答は「順序・重複の保証」「型の互換性」「変更できる操作」に分けると、確認する資料を絞れます。ビューとコピーの違いはListとsubListの例題でも確認できます。

関連する学習

本サイトの問題は、Java SE 17を学ぶためのオリジナル練習教材です。本試験の過去問やOracle公式の教材ではありません。編集・訂正の方針をご確認ください。