Java: utilizza il polimorfismo o i parametri di tipo limitato


17

Supponiamo che io abbia questa gerarchia di classi ...

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

E poi ho ....

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

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

Qual è la differenza quando si utilizzano parametri di tipo limitato o polimorfismo?

E quando usare l'uno o l'altro?


1
Queste due definizioni non traggono molto profitto dai parametri di tipo. Ma prova a scrivere addAnimals(List<Animal>)e ad aggiungere un elenco di gatti!
Kilian Foth,

7
Bene, per esempio, se ciascuno dei tuoi metodi precedenti restituisse qualcosa, anche quello che usa generici potrebbe restituire T, mentre l'altro potrebbe solo restituire Animale. Quindi, per la persona che usa questi metodi, nel primo caso tornerebbe esattamente quello che voleva: Dod dog = addAnimal (new Dog ()); mentre con il secondo metodo, sarebbe costretto a lanciare per ottenere un cane: Dog d = (Dog) addAnimalPoly (new Dog ());
Drago di Shivan,

La maggior parte del mio utilizzo di tipi limitati è il wallpapering sulla scarsa implementazione di generici da parte di Java (List <T> ha regole di varianza diverse rispetto a T, per esempio). In questo caso non c'è alcun vantaggio, è essenzialmente due modi di esprimere lo stesso concetto, anche se, come afferma @ShivanDragon che non significa che avete una T al compiletime invece di un animale. Puoi trattarlo solo come un animale internamente, ma puoi offrirlo come una T esternamente.
Phoshi,

Risposte:


14

Questi due esempi sono equivalenti e, di fatto, verranno compilati con lo stesso bytecode.

Esistono due modi in cui l'aggiunta di un tipo generico limitato a un metodo come nel tuo primo esempio farà qualsiasi cosa.

Passando il parametro type a un altro tipo

Queste due firme del metodo finiscono per essere le stesse nel codice byte, ma il compilatore applica la sicurezza del tipo:

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

public static void addAnimals(Collection<Animal> animals)

Nel primo caso, è consentito solo un Collection(o sottotipo) di Animal. Nel secondo caso, è consentito un Collection(o sottotipo) con un tipo generico di Animalo un sottotipo.

Ad esempio, è consentito quanto segue nel primo metodo ma non nel secondo:

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

Il motivo è che il secondo consente solo la raccolta di animali, mentre il primo consente la raccolta di qualsiasi oggetto assegnabile all'animale (ad es. Sottotipi). Nota che se questo elenco fosse un elenco di animali che contenevano un gatto, entrambi i metodi lo accetterebbero: il problema è la specifica generica della raccolta, non ciò che contiene effettivamente.

Restituzione di oggetti

L'altra volta è importante restituire oggetti. Supponiamo che esistesse il seguente metodo:

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

Saresti in grado di fare quanto segue con esso:

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

Mentre questo è un esempio inventato, ci sono casi in cui ha senso. Senza generici, il metodo dovrebbe tornare Animale sarebbe necessario aggiungere il tipo casting per farlo funzionare (che è ciò che il compilatore aggiunge comunque al codice byte dietro le quinte).


" Nel primo caso, è consentita solo una Collezione (o sottotipo) di Animali. Nel secondo caso, è consentita una Collezione (o sottotipo) con un tipo generico di Animale o un sottotipo. " - Vuoi raddoppiare- controlla la tua logica lì?
Fund Monica's Lawsuit

3

Usa generici invece di downcasting. Il "downcasting" è negativo, passando da un tipo più generale a uno più specifico:

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

... ti fidi che asia un gatto, ma il compilatore non può garantirlo. Potrebbe rivelarsi un cane in fase di esecuzione.

Ecco dove useresti generici:

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

Ora puoi specificare che vuoi un cacciatore di gatti:

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

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

Ora il compilatore può garantire che hunterC catturerà solo i gatti e hunterD catturerà solo i cani.

Quindi basta usare il polimorfismo regolare se si desidera solo gestire classi specifiche come tipo di base. L'upgrade è una buona cosa. Ma se ti trovi in ​​una situazione in cui devi gestire classi specifiche come il loro tipo, in generale, usa generici.

O, davvero, se scopri che devi effettuare il downcast, usa i generici.

EDIT: il caso più generale è quando si desidera rimandare la decisione su quali tipi di tipi gestire. Quindi i tipi diventano un parametro, così come i valori.

Di 'che voglio che la mia classe Zoo gestisca Gatti o Spugne. Non ho una superclasse comune. Ma posso ancora usare:

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

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

il grado in cui lo blocchi è dipende da cosa stai cercando di fare;)


2

Questa domanda è vecchia, ma un fattore importante da considerare sembra essere stato escluso riguardo a quando usare il polimorfismo rispetto ai parametri di tipo limitato. Questo fattore può essere leggermente tangente all'esempio fornito nella domanda ma, ritengo, molto rilevante per il più generale "Quando usare il polimorfismo rispetto ai parametri di tipo limitato?"

TL; DR

Se mai ti ritrovi a spostare il codice da una sottoclasse a una classe base contro il tuo miglior giudizio, a causa dell'incapacità di accedervi in ​​modo polimorfico, i parametri di tipo limitati potrebbero essere una potenziale soluzione.

La risposta completa

I parametri di tipo limitato possono esporre metodi di sottoclasse concreti e non ereditati per una variabile membro ereditata. Il polimorfismo non può

Per elaborare estendendo il tuo esempio:

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

Se la classe astratta AnimalOwner è stata definita per avere un protected Animal pet;e optare per il polimorfismo, il compilatore si guasterà sulla pet.scratchBelly();linea, indicando che questo metodo non è definito per Animal.


1

Nel tuo esempio, non devi (e non dovresti) usare il tipo limitato. Utilizzare i parametri di tipo limitati solo quando è necessario , poiché sono più confusi da comprendere.

Ecco alcune situazioni in cui utilizzerai parametri di tipo limitato:

  • Parametri delle raccolte

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

    allora puoi chiamare

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

    zoo.add(dogs)non verrebbe compilato senza <? extends Animal>, perché i generici non sono covarianti.

  • sottoclasse

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

    per limitare il tipo che la sottoclasse può fornire.

È inoltre possibile utilizzare più limiti <T extends A1 & A2 & A3>per assicurarsi che un tipo sia un sottotipo di tutti i tipi nell'elenco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.