Menu
Coddy logo textTech

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

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

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

unbounded wildcard ?は未知の型を表します。任意のパラメーター化された型を受け入れたい場合に便利です。

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 のスーパータイプです

役立つ指針:読み取り(producer)のみを行う場合は extends を使い、書き込み(consumer)のみを行う場合は super を使います。これは PECS 原則として知られています。Producer Extends、Consumer Super です。

challenge icon

チャレンジ

簡単

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

コードを4つのファイルに分けて整理します。

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

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

    calculateTotalFood(List<? extends Food> foods) - 上限境界ワイルドカードを使用して、任意のFood型のリストから読み取ります。itemの個数をintとして返します。これはproducerからの読み取りを示します。

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

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

    まず、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はコレクションに書き込む場合に使用します!

自分で試してみよう

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: Print "Food inventory:" then call printInventory
        
        // TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
        
        // TODO: meatStock という名前の新しい ArrayList<Food> を作成する
        
        // TODO: Print blank line, then "Adding to meat stock..."
        
        // TODO: meatStock と "Beef" を使って addMeatToStock を呼び出す
        
        // TODO: Print "Meat stock after adding:" then call printInventory on meatStock
    }
}
quiz icon腕試し

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

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

自分で練習してみよう: Javaオンラインコンパイラ