Menu
Coddy logo textTech

ワイルドカード (?, extends, super)

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 57/87。

境界付き型パラメータはジェネリクスを特定の型に制限しますが、既存のジェネリック型を扱っていて、正確な型パラメータがわからない、あるいは気にする必要がない場合はどうすればよいでしょうか?ここでワイルドカードの出番です。

非境界ワイルドカード ? は、未知の型を表します。これは、任意のパラメータ化された型を受け入れたい場合に便利です:

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(new ArrayList<String>());   // 動作します
printList(new ArrayList<Integer>());  // 動作します

上限付きワイルドカード ? extends Type は、指定された型またはそのサブクラスのいずれかを受け入れます。ジェネリック構造から読み取る必要がある場合に使用します:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // Integerのリスト
sumNumbers(Arrays.asList(1.5, 2.5));       // Doubleのリスト

下限付きワイルドカード ? super Type は、指定された型またはそのスーパークラスのいずれかを受け入れます。ジェネリック構造に書き込む必要がある場合に使用します:

public static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // 動作します - NumberはIntegerのスーパークラスです

役立つガイドライン:読み取りのみを行う(プロデューサー)場合は extends を使用し、書き込みのみを行う(コンシューマー)場合は super を使用します。これは PECS 原則(Producer Extends, Consumer Super)として知られています。

challenge icon

チャレンジ

簡単

3つのすべての種類のワイルドカードを実演する動物園の給餌システムを構築しましょう!動物と食べ物の階層を作成し、非境界、上限境界、下限境界のワイルドカードを使用して、さまざまな給餌シナリオを処理するユーティリティメソッドを作成します。

コードを4つのファイルに分けて構成します:

  • Food.java: 食べ物のためのシンプルなクラス階層を作成します。まず、プライベートフィールド name (String)、それを初期化するコンストラクタ、および getName() メソッドを持つベースクラス Food を作成します。次に、同じファイル内に2つのサブクラスを作成します:Meat は Food を継承し(コンストラクタは super(name) を呼び出す)、Vegetable は Food を継承します(同じパターン)。
  • Animal.java: プライベートフィールド species (String)、コンストラクタ、および getSpecies() メソッドを持つベースクラス Animal を作成します。2つのサブクラス CarnivoreHerbivore を作成し、それぞれ Animal を継承して親コンストラクタを呼び出します。
  • ZooFeeder.java: ここでワイルドカードが真価を発揮します!3つの static メソッドを持つユーティリティクラスを作成します:

    printInventory(List<?> items) - 非境界ワイルドカードを使用して、任意のリストを出力します。ループ処理を行い、toString() を使用して各項目を1行に1つずつ出力します。

    calculateTotalFood(List<? extends Food> foods) - 上限境界ワイルドカードを使用して、任意の Food 型のリストから読み取ります。項目の数を int として返します。これはプロデューサー(生産者)からの読み取りを実演します。

    addMeatToStock(List<? super Meat> stock, String meatName) - 下限境界ワイルドカードを使用して、Meat を保持できるリストに書き込みます。指定された名前で新しい Meat オブジェクトを作成し、それを stock に追加します。これはコンシューマー(消費者)への書き込みを実演します。

  • Main.java: 動物園の給餌システムを完成させましょう!肉の名前 (String) と野菜の名前 (String) の2つの入力を受け取ります。

    まず、ArrayList<Food> を作成し、肉の名前を持つ Meat オブジェクトと、野菜の名前を持つ Vegetable オブジェクトを追加します。Food inventory: と出力してから、このリストを引数に printInventory を呼び出します。

    次に、空行を出力し、calculateTotalFood を使用して Total food items: [count] と出力します。

    最後に、meatStock という名前の新しい ArrayList<Food> を作成します。空行を出力し、次に Adding to meat stock... と出力します。このリストと文字列 "Beef" を指定して addMeatToStock を呼び出します。その後、Meat stock after adding: と出力し、meat stock に対して printInventory を呼び出します。

肉の名前と野菜の名前の2つの入力を順番に受け取ります。

出力が正しく機能するように、Food クラスで toString() をオーバーライドして食べ物の名前を返すようにしてください。各ワイルドカードの型がどのように異なる目的を果たすかに注目してください:単に Object として読み取る必要がある場合の柔軟性を最大にする ?、特定の型を読み取る場合の ? extends、そしてコレクションに書き込む場合の ? super です!

チートシート

Javaは、正確な型パラメータが不明または無関係な場合にジェネリック型を扱うための3種類のワイルドカードを提供しています。

非限定ワイルドカード (?)

未知の型を表し、任意のパラメータ化された型を受け入れます:

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(new ArrayList<String>());   // 動作します
printList(new ArrayList<Integer>());  // 動作します

上限境界ワイルドカード (? extends Type)

指定された型またはそのサブクラスのいずれかを受け入れます。ジェネリック構造からデータを読み取る必要がある場合に使用します:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // Integerのリスト
sumNumbers(Arrays.asList(1.5, 2.5));       // Doubleのリスト

下限境界ワイルドカード (? super Type)

指定された型またはそのスーパークラスのいずれかを受け入れます。ジェネリック構造にデータを書き込む必要がある場合に使用します:

public static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // 動作します - NumberはIntegerのスーパークラスです

PECS原則

Producer Extends, Consumer Super: 読み取りのみを行う場合(プロデューサー)はextendsを使用し、書き込みのみを行う場合(コンシューマー)はsuperを使用します。

自分で試してみよう

import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String meatName = scanner.nextLine();
        String vegetableName = scanner.nextLine();
        
        // TODO: ArrayList<Food> を作成し、Meat と Vegetable オブジェクトを追加する
        
        // TODO: "Food inventory:" を出力し、その後 printInventory を呼び出す
        
        // TODO: 空行を出力し、次に calculateTotalFood を使用して "Total food items: [count]" を出力する
        
        // TODO: meatStock という名前の新しい ArrayList<Food> を作成する
        
        // TODO: 空行を出力し、次に "Adding to meat stock..." を出力する
        
        // TODO: meatStock と "Beef" を引数に指定して addMeatToStock を呼び出す
        
        // TODO: "Meat stock after adding:" を出力し、その後 meatStock に対して printInventory を呼び出す
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン