Java: utilizza il polimorfismo oi parametri di tipo limitato

17

Supponiamo di avere 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?

    
posta HugoMelo 06.02.2014 - 12:37
fonte

4 risposte

13

Questi due esempi sono equivalenti e infatti verranno compilati allo stesso bytecode.

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

Passaggio del parametro type a un altro tipo

Queste due firme di metodo finiscono per essere le stesse nel codice byte, ma il compilatore impone 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 Animal o un sottotipo.

Ad esempio, il seguente è permesso 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 collezioni di animali, mentre il primo consente raccolte di qualsiasi oggetto che è assegnabile ad animali (cioè sottotipi). Nota che se questa lista fosse un elenco di animali che contenevano un gatto, entrambi i metodi lo accetterebbero: il problema è la specifica generica della collezione, non ciò che in realtà contiene.

Restituzione di oggetti

L'altra volta che conta è con la restituzione degli oggetti. Supponiamo che il seguente metodo esistesse:

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

Sarai in grado di eseguire le seguenti operazioni:

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

Sebbene questo sia un esempio forzato, ci sono casi in cui ha senso. Senza generici, il metodo dovrebbe restituire Animal e sarà necessario aggiungere cast di tipo per farlo funzionare (che è ciò che il compilatore aggiunge al codice byte comunque dietro le quinte).

    
risposta data 04.03.2014 - 22:41
fonte
3

Usa i farmaci generici anziché il downcasting. "Downcasting" è brutto, 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 stai fidando che a è un gatto, ma il compilatore non può garantirlo. Potrebbe rivelarsi un cane in fase di runtime.

Ecco dove useresti i 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 usa il polimorfismo regolare se vuoi semplicemente gestire classi specifiche come tipo di base. L'upcasting è una buona cosa. Ma se ti trovi in una situazione in cui devi gestire classi specifiche come il loro tipo, in genere, usa i generici.

In realtà, se trovi che devi downcast, usa i generici.

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

Dire che voglio che la mia classe Zoo gestisca gatti o spugne. Non ho una super classe 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;)

    
risposta data 05.03.2014 - 11:08
fonte
2

Questa domanda è obsoleta, ma sembra essere stato trascurato un fattore importante da tenere in considerazione quando utilizzare i parametri di tipo polimorfismo vs limitato. Questo fattore può essere leggermente tangente rispetto all'esempio fornito nella domanda ma, ritengo, molto rilevante per il più generale "Quando utilizzare il polimorfismo rispetto ai parametri di tipo limitato?"

TL; DR

Se 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 del tipo limitato potrebbero essere una potenziale soluzione.

La risposta completa

I parametri del tipo limitato possono esporre metodi di sottoclasse concreti 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 commetterà un errore sulla riga pet.scratchBelly(); , che ti dice che questo metodo non è definito per Animal.

    
risposta data 17.11.2016 - 08:34
fonte
1

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

Ecco alcune situazioni in cui utilizzerai parametri di tipo limitato:

  • Parametri collezioni

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

    quindi puoi chiamare

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

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

  • Subclassing

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

    per limitare il tipo che la sottoclasse può fornire.

Puoi anche utilizzare più limiti <T extends A1 & A2 & A3> per garantire che un tipo sia un sottotipo di tutti i tipi nell'elenco.

    
risposta data 05.03.2014 - 10:52
fonte

Leggi altre domande sui tag