このクラス階層があるとしましょう...
public abstract class Animal {
public abstract void eat();
public abstract void talk();
}
class Dog extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
class Cat extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
そして私は....
public static <T extends Animal> void addAnimal(T animal) {
animal.eat();
animal.talk();
}
public static void addAnimalPoly(Animal animal) {
animal.eat();
animal.talk();
}
有界型パラメーターまたは多態性を使用する場合の違いは何ですか?
そして、どちらを使用するのですか?
これら2つの例は同等であり、実際には同じバイトコードにコンパイルされます。
最初の例のように、メソッドに制限付きジェネリック型を追加すると、2つの方法があります。
型パラメーターを別の型に渡す
これらの2つのメソッドシグネチャはバイトコードで同じになりますが、コンパイラは型の安全性を適用します。
public static <T extends Animal> void addAnimals(Collection<T> animals)
public static void addAnimals(Collection<Animal> animals)
最初のケースでは、Collection
(またはサブタイプ)のAnimal
のみが許可されます。 2番目のケースでは、ジェネリック型Collection
またはサブタイプのAnimal
(またはサブタイプ)が許可されます。
たとえば、以下は最初のメソッドでは許可されていますが、2番目のメソッドでは許可されていません。
List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);
その理由は、2つ目は動物のコレクションのみを許可し、1つ目は動物に割り当て可能な任意のオブジェクト(つまりサブタイプ)のコレクションを許可するためです。このリストがたまたま猫を含んでいる動物のリストである場合、どちらの方法でもそれを受け入れることに注意してください。問題はコレクションの一般的な仕様であり、実際に含まれているものではありません。
オブジェクトを返す
他に重要なのは、オブジェクトを返すことです。次のメソッドが存在すると仮定します。
public static <T extends Animal> T feed(T animal) {
animal.eat();
return animal;
}
あなたはそれを使って次のことができるでしょう:
Cat c1 = new Cat();
Cat c2 = feed(c1);
これは不自然な例ですが、意味がある場合もあります。ジェネリックスがない場合、メソッドはAnimal
を返す必要があり、それを機能させるために型キャストを追加する必要があります(コンパイラーが舞台裏でバイトコードに追加するものです)。
ダウンキャストの代わりにジェネリックを使用します。 「ダウンキャスティング」は悪く、より一般的なタイプからより具体的なタイプに移行します。
Animal a = hunter.captureOne();
Cat c = (Cat)a; // ACK!!!!!! What if it's a Dog? ClassCastException!
...a
が猫であると信頼しているが、コンパイラはそれを保証できない。実行時に犬になるかもしれません。
ここでジェネリックを使用します。
public class <A> Hunter() {
public A captureOne() { ... }
}
これで、猫ハンターが欲しいと指定できます:
Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();
Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();
これで、コンパイラはguranteeを実行でき、hunterCは猫のみをキャプチャし、hunterDは犬のみをキャプチャします。
そのため、特定のクラスを基本型として扱いたい場合は、通常のポリモーフィズムを使用してください。アップキャストは良いことです。ただし、特定のクラスを独自のタイプとして処理する必要がある場合は、総称的に総称を使用します。
または、実際には、ダウンキャストする必要がある場合はジェネリックを使用します。
編集:より一般的なケースは、処理する型の種類の決定を延期したい場合です。つまり、typesは、値と同様にパラメータになります。
ZooクラスでCatsまたはSpongesを処理したいとします。共通のスーパークラスはありません。しかし、私はまだ使用できます:
public class <T> Zoo() { ... }
Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...
ロックダウンの程度は、何をしようとしているかによって異なります;)
この質問は古臭いですが、多型対有界型パラメーターをいつ使用するかに関して考慮すべき重要な要素が除外されているようです。この要素は、質問で与えられた例にわずかに接しているかもしれませんが、より一般的な「多態性対有界型パラメーターをいつ使用するか」に非常に関連していると思います。
ポリモーフィックな方法でアクセスできないために、適切な判断に反してコードをサブクラスからベースクラスに移動する場合は、境界型パラメーターが潜在的な解決策になる可能性があります。
バインドされた型パラメーターは、継承されたメンバー変数の具体的な非継承サブクラスメソッドを公開できます。ポリモーフィズムはできません
例を拡張して詳しく説明するには:
_public abstract class AnimalOwner<T extends Animal> {
protected T pet;
public abstract void rewardPet();
}
// Modify the dog class
class Dog extends Animal {
// ...
// This method is not inherited from anywhere!
public void scratchBelly() {
System.out.println("Belly: Scratched");
}
}
class DogOwner extends AnimalOwner<Dog> {
DogOwner(Dog dog) {
this.pet = dog;
}
@Override
public void rewardPet()
{
// ---- Note this call ----
pet.scratchBelly();
}
}
_
抽象クラスのAnimalOwnerが_protected Animal pet;
_を持ち、ポリモーフィズムを選択するように定義されている場合、コンパイラはpet.scratchBelly();
行でエラーを表示し、このメソッドがAnimalに対して未定義であることを通知します。
あなたの例では、境界型を使用しないでください(使用すべきではありません)。 理解するのがより混乱するため、必要な場合にのみ、境界タイプパラメータを使用してください。
制限された型パラメーターを使用するいくつかの状況を次に示します。
コレクションパラメータ
_class Zoo {
private List<Animal> animals;
public void add(Collection<? extends Animal> newAnimals) {
animals.addAll(newAnimals);
}
}
_
その後、あなたは呼び出すことができます
_List<Dog> dogs = ...
Zoo.add(dogs);
_
ジェネリックは共変ではないため、Zoo.add(dogs)
は_<? extends Animal>
_なしではコンパイルに失敗します。
サブクラス化
_abstract class Warrior<T extends Weapon> {
public abstract T getWeapon();
}
_
サブクラスが提供できるタイプを制限します。
複数の境界_<T extends A1 & A2 & A3>
_を使用して、型がリスト内のすべての型のサブタイプであることを確認することもできます。