Java - Verwenden Sie Polymorphismus oder begrenzte Typparameter


17

Angenommen, ich habe diese Klassenhierarchie ...

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

Und dann habe ich ....

public static <T extends Animal> void addAnimal(T animal) {
    animal.eat();
    animal.talk();
}

public static void addAnimalPoly(Animal animal) {
    animal.eat();
    animal.talk();
}

Was ist der Unterschied bei Verwendung von begrenzten Typparametern oder Polymorphismus?

Und wann benutzt man das eine oder andere?


1
Diese beiden Definitionen profitieren nicht sehr von Typparametern. Aber versuchen Sie, addAnimals(List<Animal>)eine Liste der Katzen zu schreiben und hinzuzufügen!
Kilian Foth

7
Nun, zum Beispiel, wenn jede der oben genannten Methoden auch etwas zurückgeben würde, dann könnte die eine, die Generika verwendet, T zurückgeben, während die andere nur Animal zurückgeben könnte. Für die Person, die diese Methoden anwendet, würde sie im ersten Fall genau das zurückbekommen, was sie wollte: Dod dog = addAnimal (new Dog ()); Bei der zweiten Methode müsste er zaubern, um einen Hund zu bekommen: Dog d = (Dog) addAnimalPoly (new Dog ());
Shivan Dragon

Der Großteil meiner Verwendung beschränkter Typen ist das Tapezieren von Javas schlechter Implementierung von Generika (List <T> hat beispielsweise andere Varianzregeln als T allein). In diesem Fall gibt es keinen Vorteil, es ist im Wesentlichen zwei Möglichkeiten, das gleiche Konzept zum Ausdruck, obwohl , wie @ShivanDragon heißt es , hat man ein T an compiletime zu bedeuten haben anstelle eines Tieres. Sie können es nur intern wie ein Tier behandeln, aber Sie können es extern als T anbieten.
Phoshi

Antworten:


14

Diese beiden Beispiele sind äquivalent und werden tatsächlich mit demselben Bytecode kompiliert.

Es gibt zwei Möglichkeiten, einer Methode einen begrenzten generischen Typ hinzuzufügen, wie im ersten Beispiel beschrieben.

Übergeben des Typparameters an einen anderen Typ

Diese beiden Methodensignaturen sind im Bytecode identisch, der Compiler erzwingt jedoch die Typensicherheit:

public static <T extends Animal> void addAnimals(Collection<T> animals)

public static void addAnimals(Collection<Animal> animals)

Im ersten Fall ist nur ein Collection(oder ein Subtyp) von Animalzulässig. Im zweiten Fall ist ein Collection(oder ein Subtyp) mit einem generischen Typ Animaloder einem Subtyp zulässig.

Beispielsweise ist in der ersten Methode Folgendes zulässig, in der zweiten jedoch nicht:

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

Der Grund ist, dass der zweite nur Sammlungen von Tieren zulässt, während der erste Sammlungen von Objekten zulässt, die einem Tier zugeordnet werden können (dh Subtypen). Wenn diese Liste eine Liste von Tieren wäre, die zufällig eine Katze enthielten, würde dies von beiden Methoden akzeptiert: Das Problem ist die generische Spezifikation der Sammlung, nicht das, was sie tatsächlich enthält.

Gegenstände zurückgeben

Das andere Mal ist es wichtig, Objekte zurückzugeben. Nehmen wir an, dass folgende Methode existiert:

public static <T extends Animal> T feed(T animal) {
  animal.eat();
  return animal;
}

Damit könnten Sie Folgendes tun:

Cat c1 = new Cat();
Cat c2 = feed(c1);

Obwohl dies ein erfundenes Beispiel ist, gibt es Fälle, in denen es sinnvoll ist. Ohne Generika müsste die Methode zurückkehren Animalund Sie müssten Typumwandlung hinzufügen, damit sie funktioniert (was der Compiler hinter den Kulissen sowieso zum Bytecode hinzufügt).


" Im ersten Fall ist nur eine Sammlung (oder ein Subtyp) von Animal zulässig. Im zweiten Fall ist eine Sammlung (oder ein Subtyp) mit einem generischen Typ von Animal oder einem Subtyp zulässig. " Überprüfen Sie Ihre Logik dort?
Fund Monica's Lawsuit

3

Verwenden Sie Generika anstelle von Downcasting. "Downcasting" ist schlecht und geht von einem allgemeineren Typ zu einem spezifischeren über:

Animal a = hunter.captureOne();
Cat c = (Cat)a;  // ACK!!!!!! What if it's a Dog? ClassCastException!

... Sie vertrauen darauf, dass aes sich um eine Katze handelt, aber der Compiler kann dies nicht garantieren. Es könnte sich zur Laufzeit herausstellen, dass es sich um einen Hund handelt.

Hier ist, wo Sie Generika verwenden würden:

public class <A> Hunter() {
    public A captureOne() { ... }
}

Jetzt können Sie festlegen, dass Sie einen Katzenjäger wollen:

Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();

Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();

Jetzt kann der Compiler garantieren, dass hunterC nur Katzen und hunterD nur Hunde erfasst.

Verwenden Sie also einfach den regulären Polymorphismus, wenn Sie nur bestimmte Klassen als Basistyp verwenden möchten. Upcasting ist eine gute Sache. Aber wenn Sie in einer Situation sind, in der Sie bestimmte Klassen als ihren eigenen Typ behandeln müssen, verwenden Sie generische Klassen.

Oder wirklich, wenn Sie feststellen, dass Sie niedergeschlagen werden müssen, verwenden Sie Generika.

BEARBEITEN: Der allgemeinere Fall ist, wenn Sie die Entscheidung darüber aufschieben möchten, welche Arten von Typen behandelt werden sollen. So werden sowohl die Typen als auch die Werte zu Parametern.

Angenommen, ich möchte, dass meine Zoo-Klasse mit Katzen oder Schwämmen umgeht. Ich habe keine gemeinsame Superklasse. Aber ich kann immer noch verwenden:

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

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

Inwieweit Sie dies sperren, hängt davon ab, was Sie tun möchten.


2

Diese Frage ist altmodisch, aber ein wichtiger Faktor, der zu berücksichtigen ist, scheint in Bezug auf die Verwendung von Polymorphismus gegenüber begrenzten Typparametern ausgelassen worden zu sein. Dieser Faktor mag sich leicht auf das in der Frage angegebene Beispiel auswirken, ist jedoch meiner Meinung nach für das allgemeinere Thema "Wann ist Polymorphismus gegenüber begrenzten Typparametern anzuwenden?" Von großer Bedeutung.

TL; DR

Wenn Sie jemals feststellen sollten, dass Sie den Code von einer Unterklasse in eine Basisklasse verschieben, weil Sie nicht mehr polymorph darauf zugreifen können, könnten begrenzte Typparameter eine mögliche Lösung sein.

Die vollständige Antwort

Mit begrenzten Typparametern können konkrete, nicht vererbte Unterklassenmethoden für eine vererbte Elementvariable verfügbar gemacht werden. Polymorphismus kann nicht

So erweitern Sie Ihr Beispiel:

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

Wenn für die abstrakte Klasse AnimalOwner ein protected Animal pet;und für Polymorphismus festgelegt wurde, gibt der Compiler in der pet.scratchBelly();Zeile einen Fehler aus und teilt Ihnen mit, dass diese Methode für Animal nicht definiert ist.


1

In Ihrem Beispiel verwenden Sie keinen begrenzten Typ (und sollten ihn auch nicht verwenden). Verwenden Sie nur begrenzte Typparameter, wenn dies erforderlich ist , da das Verständnis der Parameter verwirrender ist.

In der folgenden Situation verwenden Sie begrenzte Typparameter:

  • Sammlungsparameter

    class Zoo {
    
      private List<Animal> animals;
    
      public void add(Collection<? extends Animal> newAnimals) {
        animals.addAll(newAnimals);
      }
    }

    dann kannst du anrufen

    List<Dog> dogs = ...
    zoo.add(dogs);

    zoo.add(dogs)würde nicht ohne kompilieren <? extends Animal>, weil generika nicht kovariant sind.

  • Unterklassen

    abstract class Warrior<T extends Weapon> {
    
      public abstract T getWeapon();
    }

    um den Typ einzuschränken, den die Unterklasse bereitstellen kann.

Sie können auch mehrere Schranken verwenden, <T extends A1 & A2 & A3>um sicherzustellen, dass ein Typ ein Untertyp aller Typen in der Liste ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.