Java-ポリモーフィズムまたは境界型パラメーターを使用する


17

このクラス階層があるとします...

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();
}

制限された型パラメーターまたはポリモーフィズムを使用する場合の違いは何ですか?

そして、いつどちらを使用するのですか?


1
これら2つの定義は、型パラメーターからあまり利益を得ません。しかしaddAnimals(List<Animal>)、猫のリストを書いて追加してみてください!
キリアンフォス14

7
たとえば、上記の各メソッドが何かを返す場合、ジェネリックを使用するメソッドはTを返し、もう一方はAnimalのみを返すことができます。したがって、これらのメソッドを使用している人にとって、最初のケースでは、彼は自分が望んでいたものを正確に戻すことができます。Dod dog = addAnimal(new Dog()); 一方、2番目の方法では、犬を取得するためにキャストを強制されます。Dog d =(Dog)addAnimalPoly(new Dog());
シヴァンドラゴン14

私の境界型の使用の大部分は、Javaのジェネリックの貧弱な実装に壁紙を張ることです(たとえば、List <T>にはTだけと異なる分散ルールがあります)。この場合、@ShivanDragonを述べて、それはけれども、それは、本質的に同じ概念を表現する二つの方法だ、何のメリットがないあなたの代わりに動物のコンパイル時Tを持っている意味。内部的には動物のようにしか扱えませんが、外部的にはTとして提供できます。
Phoshi

回答:


14

これら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必要があり、型キャストを追加して動作させる必要があります(これは、コンパイラが舞台裏でバイトコードに追加するものです)。


最初のケースでは、動物のコレクション(またはサブタイプ)のみが許可されます。2番目のケースでは、動物またはジェネリックのジェネリックタイプを持つコレクション(またはサブタイプ)が許可されます。」そこでロジックを確認しますか?
ファンドモニカの訴訟

3

ダウンキャストの代わりにジェネリックを使用します。「ダウンキャスティング」は悪いもので、より一般的なタイプからより具体的なタイプに移行します

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();

コンパイラは、hunterCが猫のみを捕獲し、hunterDが犬のみを捕獲することを保証できます。

そのため、特定のクラスを基本型として処理するだけの場合は、通常のポリモーフィズムを使用します。アップキャスティングは良いことです。ただし、特定のクラスを独自の型として扱う必要がある場合は、ジェネリックを使用します。

または、本当に、ダウンキャストする必要があることがわかったら、ジェネリックを使用します。

編集:より一般的なケースは、処理するタイプの種類の決定を先送りしたい場合です。そのため、はパラメーターになり、値にもなります。

ZooクラスでCatsまたはSpongesを処理したいとします。共通のスーパークラスはありません。しかし、私はまだ使用できます:

public class <T> Zoo() { ... }

Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...

あなたがそれをロックする程度は、あなたが何をしようとしているかに依存します;)


2

この質問は古いものですが、ポリモーフィズムと境界型パラメーターをいつ使用するかについて考慮すべき重要な要素が残されているようです。この要素は、質問で与えられた例にわずかに接しているかもしれませんが、私は、より一般的な「ポリモーフィズムと境界型パラメータをいつ使用するか」に非常に関連があると感じます。

TL; DR

ポリモーフィックな方法でアクセスできないために、コードのサブクラスからベースクラスにコードを適切な判断で移動することに気付いた場合、有界型パラメーターが解決策になる可能性があります。

完全な答え

境界型パラメーターは、継承されたメンバー変数の具体的な非継承サブクラスメソッドを公開できます。多態性はできません

例を拡張して詳細に説明するには:

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がaを持ち、protected Animal pet;ポリモーフィズムを選択するように定義されている場合、コンパイラはpet.scratchBelly();行でエラーを発生し、このメソッドがAnimalに対して未定義であることを通知します。


1

この例では、境界型を使用しません(使用すべきではありません)。わかりにくいので、必要な場合にのみ境界型パラメーターを使用してください

バインドされた型パラメーターを使用する状況を次に示します。

  • コレクションのパラメーター

    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>を使用して、タイプがリスト内のすべてのタイプのサブタイプであることを確認することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.