CodeGym /Java Blog /ランダム /ジェネリックのワイルドカード
John Squirrels
レベル 41
San Francisco

ジェネリックのワイルドカード

ランダム グループに公開済み
やあ!ジェネリック医薬品の研究を続けましょう。これまでのレッスン (ジェネリックスを操作するときの可変引数の使用型消去について)から、それらについてのかなりの知識をすでに得ていますが、まだ考慮していない重要なトピックがあります —ワイルドカード。これはジェネリックの非常に重要な機能です。非常に多くのことを考慮して、別のレッスンを用意しました。とはいえ、ワイルドカードについては特に複雑なことはありません。それはすぐにわかります:)ジェネリックのワイルドカード - 1例を見てみましょう:

public class Main {

   public static void main(String[] args) {
      
       String str = new String("Test!");
       // No problem
       Object obj = str;
      
       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
何が起きてる?非常に似た状況が 2 つあります。この場合、StringオブジェクトをオブジェクトにキャストしますObject。ここには問題はありません。すべてが期待どおりに機能します。しかし、2 番目の状況では、コンパイラはエラーを生成します。でも、私たちもやっていることは同じですよね?今回は単に複数のオブジェクトのコレクションを使用しています。しかし、なぜエラーが発生するのでしょうか? 違いは何ですか? String1 つのオブジェクトを 1 つのオブジェクトにキャストするのでしょうかObject、それとも 20 のオブジェクトにキャストするのでしょうか? オブジェクトオブジェクトのコレクションの間には重要な違いがあります。 クラスが クラスの子である場合、 はの子ではありません。BACollection<B>Collection<A>List<String>これが、に キャストできなかった理由です。List<Object>Stringは の子ですObjectが、List<String>の子ではありませんList<Object> これは非常に直感的ではないように思えるかもしれません。この言語の作成者はなぜこのようにしたのでしょうか? コンパイラがエラーを返さなかったと想像してみましょう。

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
この場合、たとえば次のようにすることができます。

objects.add(new Object());
String s = strings.get(0);
コンパイラではエラーが発生せず、List<Object>を指す参照の作成が許可されたためstrings、古いObjectオブジェクトをstringsコレクションに追加できます。したがって、コレクションにはジェネリック型呼び出しの type 引数で指定されたオブジェクトのみが含まれるという保証が失われますString。言い換えれば、ジェネリックの主な利点である型安全性が失われているということです。そして、コンパイラはこれを阻止しなかったので、実行時にのみエラーが発生しますが、これは常にコンパイル エラーよりもはるかに悪いものです。このような状況を防ぐために、コンパイラはエラーを返します。

// Compilation error
List<Object> objects = strings;
...そして、それがList<String>の子孫ではないことを思い出させますList<Object>。これはジェネリック医薬品の鉄則であり、ジェネリック医薬品を扱う際には必ず覚えておいてください。次へ移りましょう。小さなクラス階層があるとします。

public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
階層の最上位には単純な Animal クラスがあり、これは Pet に継承されます。ペットには、Dog と Cat の 2 つのサブクラスがあります。ここで、単純なメソッドを作成する必要があるとしますiterateAnimals()Animalこのメソッドは、任意の動物 ( 、PetCat、 )のコレクションを取得しDog、すべての要素を反復処理し、各反復中にコンソールにメッセージを表示する必要があります。そのようなメソッドを書いてみましょう。

public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
問題は解決したようです!ただし、最近知ったように、List<Cat>List<Dog>は!List<Pet>の子孫ではありません。これは、猫のリストを指定してメソッドを List<Animal>呼び出そうとすると、コンパイル エラーが発生することを意味します。iterateAnimals()

import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       // Compilation error!
       iterateAnimals(cats);
   }
}
状況は私たちにとってあまり良くないようです!動物の種類ごとに列挙するためのメソッドを個別に記述する必要がありますか? 実際には、いいえ、そうではありません :) そして偶然にも、ワイルドカードがこれに役立ちます。次の構成を使用する 1 つの簡単な方法で問題を解決できます。

public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
これはワイルドカードです。より正確には、これは数種類のワイルドカードのうちの最初のものです。これは上限付きワイルドカードとして知られており、 ?で表現されます。延長します。この構造は何を教えてくれるでしょうか? これは、メソッドがAnimalオブジェクトのコレクション、または派生クラスAnimal(? Animal を拡張) のオブジェクトのコレクションを受け入れることを意味します。つまり、メソッドはAnimalPetDog、またはCatオブジェクトのコレクションを受け入れることができます。これには違いはありません。それが機能することを自分自身に納得させましょう。

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
コンソール出力:

Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
合計 4 つのコレクションと 8 つのオブジェクトを作成しました。コンソールにはちょうど 8 つのエントリがあります。すべてがうまくいきます!:) ワイルドカードを使用すると、特定の型に関連付けられた必要なロジックを 1 つのメソッドに簡単に組み込むことができました。動物の種類ごとに個別のメソッドを記述する必要がなくなりました。私たちのアプリケーションが動物園や動物病院で使用された場合、どれだけのメソッドが必要になるか想像してみてください :) しかし、今度は別の状況を見てみましょう。継承階層は変更されません。最上位クラスはAnimalPetそのすぐ下にクラスがあり、その次のレベルに とCatクラスDogがあります。ここで、犬を除くiterateAnimals()あらゆる種類の動物で機能するようにメソッドを書き直す必要があります。つまり、 を受け入れる必要があります。Collection<Animal>Collection<Pet>またはCollection<Car>、ただし、 では動作しませんCollection<Dog>。どうすればこれを達成できるでしょうか? どうやら、型ごとに個別のメソッドを作成するという見通しに再び直面しているようです :/ 他にどうやってコンパイラに私たちが望むことを説明すればよいでしょうか? 実はとてもシンプルなんです!ここでもワイルドカードが役に立ちます。ただし、今回は別のタイプのワイルドカード、つまりsuperを使用して表現される下限ワイルドカードを使用します。

public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Another iteration in the loop!");
   }
}
ここでも原理は同様です。この構成体は、メソッドがオブジェクトのコレクションまたはクラスの任意の祖先を入力として受け入れることができること<? super Cat>をコンパイラーに伝えます。この場合、クラス、その親、およびその親の親 はすべて、この説明と一致します。このクラスは制限に一致しないため、引数を指定してメソッドを使用しようとするとコンパイル エラーが発生します。 iterateAnimals()CatCatCatPetAnimalDogList<Dog>

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
  
   // Compilation error!
   iterateAnimals(dogs);
}
問題は解決しました。また、ワイルドカードが非常に便利であることがわかりました :) これで、レッスンは終了しました。これで、Java の学習においてジェネリックがいかに重要であるかがわかりました。ジェネリックについては 4 回のレッスンを受講しました。しかし、これであなたはこのテーマに精通し、就職面接で自分のスキルを証明できるようになりました :) さて、いよいよタスクに戻ります。学業の成功を祈って!:)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION