ワイルドカード (?, 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 です。
チャレンジ
簡単3種類すべてのワイルドカードを示す動物園の給餌システムを構築しましょう!動物と食べ物の階層を作成し、非境界、上限境界、下限境界のワイルドカードを使用してさまざまな給餌シナリオを処理するユーティリティメソッドを記述します。
コードを4つのファイルに分けて整理します。
Food.java: 食べ物のシンプルな階層を作成します。まず、privateフィールドname(String)、それを初期化するコンストラクター、およびgetName()メソッドを持つ基本クラスFoodから始めます。次に、同じファイル内に2つのサブクラスを作成します。MeatはFoodを継承し(コンストラクターでsuper(name)を呼び出します)、VegetableもFoodを継承します(同じパターンです)。Animal.java: privateフィールドspecies(String)、コンストラクター、およびgetSpecies()メソッドを持つ基本クラスAnimalを作成します。2つのサブクラス、CarnivoreとHerbivoreを作成し、それぞれ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
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造自分で練習してみよう: Javaオンラインコンパイラ