こんにちは!私たちはジェネリック医薬品の研究を続けています。これまでの講義 (ジェネリックスを操作するときの可変引数の使用と型消去について) ですでにそれらについての十分な知識を持っていますが、ワイルドカードという重要なトピックについてはまだ取り上げていません。これはジェネリックの非常に重要な機能です。そのため、別の講義を用意しました。ただし、ワイルドカードについては何も複雑なことはありません。これでわかります :)例を見てみましょう:
public class Main {
public static void main(String[] args) {
String str = new String("Test!");
// ниHowих проблем
Object obj = str;
List<String> strings = new ArrayList<String>();
// ошибка компиляции!
List<Object> objects = strings;
}
}
何が起きてる?非常に似た状況が 2 つあります。最初の例では、オブジェクトをString
type にキャストしようとしていますObject
。これには問題はなく、すべてが正常に機能します。しかし、2 番目の状況では、コンパイラはエラーをスローします。同じことをやっているように見えますが。ただ、今は複数のオブジェクトのコレクションを使用しているだけです。しかし、なぜエラーが発生するのでしょうか? String
本質的に、1 つのオブジェクトを 1 つの型にキャストするか、20 個のオブジェクトにキャストするかの違いは何でしょうかObject
? オブジェクトとオブジェクトのコレクションの間には重要な違いがあります。 クラスがB
クラスの後継である場合А
、Collection<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> object
コンパイラではエラーが発生せず、文字列のコレクションへの 参照を作成できるため、文字列ではなく、任意のオブジェクトstrings
を追加できます。したがって、ジェネリックで指定されたオブジェクトのみがコレクション内にあるという保証は失われます。つまり、ジェネリックスの主な利点である型安全性が失われています。そして、コンパイラによってこれらすべての処理が可能になるため、プログラムの実行中にエラーが発生するだけになりますが、これは常にコンパイル エラーよりもはるかに悪いものです。このような状況を防ぐために、コンパイラは次のエラーを返します。 strings
Object
String
// ошибка компиляции
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()");
}
}
階層の先頭にあるのは単に動物です。ペットは動物から継承されます。ペットは犬と猫の2種類に分けられます。ここで、単純なメソッドを作成する必要があると想像してくださいiterateAnimals()
。Animal
このメソッドは、任意の動物 ( 、Pet
、Cat
、 )のコレクションを受け入れDog
、すべての要素を反復処理し、毎回コンソールに何かを出力する必要があります。このメソッドを書いてみましょう:
public static void iterateAnimals(Collection<Animal> animals) {
for(Animal animal: animals) {
System.out.println("Еще один шаг в цикле пройден!");
}
}
問題は解決されたようです!しかし、私たちが最近知ったように、相続人ではないの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("Еще один шаг в цикле пройден!");
}
}
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());
//ошибка компилятора!
iterateAnimals(cats);
}
}
状況は私たちにとって良くないようです!すべての種類の動物を列挙するには個別のメソッドを作成する必要があることがわかりましたか? 実際、いいえ、その必要はありません :) そして、ワイルドカードがこれに役立ちます。次の構造を使用して、1 つの簡単な方法で問題を解決します。
public static void iterateAnimals(Collection<? extends Animal> animals) {
for(Animal animal: animals) {
System.out.println("Еще один шаг в цикле пройден!");
}
}
これがワイルドカードです。より正確には、これはいくつかのタイプのワイルドカードの最初のものです - 「extends」 (別名はUpper Bounded Wildcardsです)。このデザインは私たちに何を伝えているのでしょうか?これは、メソッドがクラス オブジェクトのコレクションAnimal
、または任意の子孫クラスのオブジェクトを入力として受け取ることを意味しますAnimal (? extends Animal)
。Animal
つまり、メソッドはコレクション、Pet
、Dog
またはを入力として受け入れることができます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);
}
コンソール出力:
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
合計 4 つのコレクションと 8 つのオブジェクトを作成しました。コンソールにはちょうど 8 つのエントリがあります。すべてがうまくいきます!:) ワイルドカードを使用すると、特定の型にバインドする必要なロジックを 1 つのメソッドに簡単に組み込むことができました。動物の種類ごとに個別のメソッドを記述する必要がなくなりました。私たちのアプリケーションが動物園や動物病院で使用された場合、どれだけのメソッドが存在するかを想像してみてください :) 次に、別の状況を見てみましょう。継承階層は変わりません。トップレベルのクラスはAnimal
、そのすぐ下にクラス Pets がありPet
、次のレベルは とCat
ですDog
。ここで、犬を除くiretateAnimals()
あらゆる種類の動物で動作できるようにメソッドを書き直す必要があります。つまり、またはを入力として受け入れる必要がありますが、 では動作しません。どうすればこれを達成できるでしょうか? 型ごとに個別のメソッドを記述する可能性が再び私たちの前に迫っているようです :/ 他にどうやってロジックをコンパイラに説明できるでしょうか? そして、これは非常に簡単に行うことができます。ここでもワイルドカードが役に立ちます。ただし、今回は別のタイプ「 super」を使用します(別の名前はLower Bounded Wildcardsです)。 Collection<Animal>
Collection<Pet>
Collection<Cat>
Collection<Dog>
public static void iterateAnimals(Collection<? super Cat> animals) {
for(int i = 0; i < animals.size(); i++) {
System.out.println("Еще один шаг в цикле пройден!");
}
}
ここでも原理は似ています。この構造は、<? super Cat>
メソッドがそのクラスまたは他の祖先クラスiterateAnimals()
のオブジェクトのコレクションを入力として受け取ることができることをコンパイラに伝えます。私たちの場合、クラス自体、その祖先、および祖先の祖先がこの説明に当てはまります。このクラスはこの制約に適合しないため、リストを含むメソッドを使用しようとするとコンパイル エラーが発生します。 Cat
Cat
Cat
Pets
Animal
Dog
List<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);
//ошибка компиляции!
iterateAnimals(dogs);
}
私たちの問題は解決され、ワイルドカードが非常に役立つことがわかりました :) これで講義は終わりです。これで、Java を学習する際にジェネリックスのトピックがいかに重要であるかがわかりました。これに 4 回の講義を費やしました。しかし、今ではこのテーマをよく理解しているので、面接で自分自身を証明できるでしょう :) そして、今度はタスクに戻る時です! 勉強頑張ってください!:)
GO TO FULL VERSION