CodeGym /Java Blog /Random-IT /Caratteri jolly nei generici
John Squirrels
Livello 41
San Francisco

Caratteri jolly nei generici

Pubblicato nel gruppo Random-IT
CIAO! Continuiamo il nostro studio sui generici. Hai già acquisito un corpo sostanziale di conoscenza su di essi dalle lezioni precedenti (sull'uso di varargs quando si lavora con i generici e sulla cancellazione del tipo ), ma c'è un argomento importante che non abbiamo ancora considerato: i caratteri jolly . Questa è una caratteristica molto importante dei generici. Tanto che gli abbiamo dedicato una lezione a parte! Detto questo, non c'è niente di particolarmente complicato nei caratteri jolly. Lo vedrai subito :) Caratteri jolly nei generici - 1Diamo un'occhiata a un esempio:

public class Main {

   public static void main(String[] args) {
      
       String str = new String("Test!");
       // No problem
       Object obj = str;
      
       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
Cosa sta succedendo qui? Vediamo due situazioni molto simili. Nel caso, lanciamo un Stringoggetto su un Objectoggetto. Non ci sono problemi qui: tutto funziona come previsto. Ma nella seconda situazione, il compilatore genera un errore. Ma stiamo facendo la stessa cosa, no? Questa volta stiamo semplicemente usando una raccolta di diversi oggetti. Ma perché si verifica l'errore? Qual è la differenza? Stiamo lanciando un Stringoggetto su uno Objecto 20 oggetti? C'è un'importante distinzione tra un oggetto e una collezione di oggetti . Se la Bclasse è figlia della Aclasse, allora Collection<B>non è figlia di Collection<A>. Questo è il motivo per cui non siamo stati in grado di lanciare il nostro List<String>aList<Object>. Stringè figlio di Object, ma List<String>non è figlio di List<Object>. Questo potrebbe non sembrare super intuitivo. Perché i creatori del linguaggio l'hanno fatto in questo modo? Immaginiamo che il compilatore non ci dia errore:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
In questo caso, potremmo, ad esempio, fare quanto segue:

objects.add(new Object());
String s = strings.get(0);
Poiché il compilatore non ci ha dato alcun errore e ci ha permesso di creare un List<Object>riferimento che punta a strings, possiamo aggiungere qualsiasi vecchio Objectoggetto alla stringsraccolta! Pertanto, abbiamo perso la garanzia che la nostra raccolta contenga solo gli Stringoggetti specificati dall'argomento type nell'invocazione di tipo generico . In altre parole, abbiamo perso il vantaggio principale dei generici: l'indipendenza dal tipo. E poiché il compilatore non ci ha impedito di farlo, avremo un errore solo in fase di esecuzione, che è sempre molto peggio di un errore di compilazione. Per evitare situazioni come questa, il compilatore ci dà un errore:

// Compilation error
List<Object> objects = strings;
...e ci ricorda che List<String>non è un discendente di List<Object>. Questa è una regola ferrea per i generici e deve essere ricordata quando si lavora con loro. Andiamo avanti. Supponiamo di avere una piccola gerarchia di classi:

public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
La gerarchia è sormontata da una semplice classe Animal, ereditata da Pet. Pet ha 2 sottoclassi: Cane e Gatto. Supponiamo ora di dover creare un iterateAnimals()metodo semplice. Il metodo dovrebbe prendere una raccolta di qualsiasi animale ( Animal, Pet, Cat, Dog), iterare su tutti gli elementi e visualizzare un messaggio sulla console durante ogni iterazione. Proviamo a scrivere un metodo del genere:

public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Sembra che il problema sia risolto! Tuttavia, come abbiamo appreso di recente, List<Cat>, List<Dog>e List<Pet>non sono discendenti di List<Animal>! Ciò significa che quando proviamo a chiamare il iterateAnimals()metodo con un elenco di gatti, otteniamo un errore di compilazione:

import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   public static void main(String[] args) {


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

       // Compilation error!
       iterateAnimals(cats);
   }
}
La situazione non sembra molto buona per noi! Dobbiamo scrivere metodi separati per enumerare ogni tipo di animale? In realtà no, non lo facciamo :) E si dà il caso che i caratteri jolly ci aiutino in questo! Possiamo risolvere il problema con un semplice metodo utilizzando il seguente costrutto:

public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Questo è un carattere jolly. Più precisamente, questo è il primo di diversi tipi di caratteri jolly. È noto come carattere jolly con limite superiore ed è espresso da ? si estende . Cosa ci dice questo costrutto? Ciò significa che il metodo accetta una raccolta di Animaloggetti o una raccolta di oggetti di qualsiasi classe che discende da Animal(? extends Animal). In altre parole, il metodo può accettare una raccolta di Animal, Pet, Dogo Catoggetti: non fa differenza. Convinciamoci che funziona:

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

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

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Uscita console:

Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Abbiamo creato un totale di 4 raccolte e 8 oggetti e ci sono esattamente 8 voci sulla console. Tutto funziona alla grande! :) Il carattere jolly ci ha permesso di adattare facilmente la logica necessaria legata a tipi specifici in un unico metodo. Abbiamo eliminato la necessità di scrivere un metodo separato per ogni tipo di animale. Immagina quanti metodi avremmo avuto bisogno se la nostra applicazione fosse stata utilizzata da uno zoo o da un ufficio veterinario :) Ma ora diamo un'occhiata a una situazione diversa. La nostra gerarchia di ereditarietà rimane invariata: la classe di primo livello è Animal, con la Petclasse appena sotto, e le classi Cate Dogal livello successivo. Ora è necessario riscrivere il iterateAnimals()metodo in modo che funzioni con qualsiasi tipo di animale, ad eccezione dei cani . Cioè, dovrebbe accettare Collection<Animal>,Collection<Pet>o Collection<Car>, ma non dovrebbe funzionare con Collection<Dog>. Come possiamo raggiungere questo risultato? Sembra che ci troviamo di nuovo di fronte alla prospettiva di scrivere un metodo separato per ogni tipo :/ In quale altro modo spieghiamo al compilatore cosa vogliamo che accada? In realtà è abbastanza semplice! Ancora una volta, i caratteri jolly vengono in nostro aiuto qui. Ma questa volta utilizzeremo un altro tipo di carattere jolly: un carattere jolly con limite inferiore , che viene espresso utilizzando super .

public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Another iteration in the loop!");
   }
}
Qui il principio è simile. Il <? super Cat>costrutto dice al compilatore che il iterateAnimals()metodo può accettare come input una collezione di Catoggetti o qualsiasi antenato della Catclasse come input. In questo caso, la Catclasse, il suo genitore Pete il genitore del suo genitore, Animalcorrispondono tutti a questa descrizione. La Dogclasse non corrisponde alla nostra restrizione, quindi un tentativo di utilizzare il metodo con un List<Dog>argomento risulterà in un errore di compilazione:

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

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

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
  
   // Compilation error!
   iterateAnimals(dogs);
}
Abbiamo risolto il nostro problema, e ancora una volta i caratteri jolly si sono rivelati estremamente utili :) Con questo, la lezione è giunta al termine. Ora vedi quanto sono importanti i generici nel tuo studio di Java: abbiamo avuto 4 intere lezioni su di loro! Ma ora conosci bene l'argomento e puoi dimostrare le tue abilità nei colloqui di lavoro :) E ora è il momento di tornare ai compiti! Il miglior successo nei tuoi studi! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION