JavaRush /Java Blog /Random-IT /Caratteri jolly nei generici Java

Caratteri jolly nei generici Java

Pubblicato nel gruppo Random-IT
Ciao! Continuiamo a studiare il tema dei farmaci generici. Hai già una buona conoscenza di questi argomenti dalle lezioni precedenti (sull'uso dei vararg quando si lavora con i generici e sulla cancellazione dei tipi ), ma non abbiamo ancora trattato un argomento importante: i caratteri jolly . Questa è una caratteristica molto importante dei farmaci generici. Tanto che gli abbiamo dedicato una lezione a parte! Tuttavia, non c'è nulla di complicato nei caratteri jolly, lo vedrai ora :) Caratteri jolly nei generici - 1Vediamo un esempio:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
Cosa sta succedendo qui? Vediamo due situazioni molto simili. Nel primo di questi, stiamo provando a lanciare un oggetto Stringdi tipo Object. Non ci sono problemi con questo, tutto funziona come dovrebbe. Ma nella seconda situazione, il compilatore genera un errore. Anche se sembrerebbe che stiamo facendo la stessa cosa. È solo che ora stiamo utilizzando una raccolta di diversi oggetti. Ma perché si verifica l'errore? Qual è la differenza, in sostanza, se trasformiamo un oggetto Stringin un tipo Objecto 20 oggetti? Esiste un'importante differenza tra un oggetto e una raccolta di oggetti . Se una classe è il successore di un'altra classe , allora non è un successore . È per questo motivo che non abbiamo potuto portare il nostro a . è un erede , ma non è un erede . Intuitivamente, questo non sembra molto logico. Perché i creatori della lingua furono guidati da questo principio? Immaginiamo che il compilatore non ci dia un errore qui: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
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);
Dato che il compilatore non ci ha dato errori e ci ha permesso di creare un riferimento List<Object> objectad una collezione di stringhe strings, possiamo aggiungere stringsnon una stringa, ma semplicemente qualsiasi oggetto Object! Pertanto, abbiamo perso la garanzia che solo gli oggetti specificati nel generico siano nella nostra collezioneString . Cioè, abbiamo perso il vantaggio principale dei farmaci generici: la sicurezza dei tipi. E poiché il compilatore ci ha permesso di fare tutto questo, significa che otterremo solo un errore durante l'esecuzione del programma, che è sempre molto peggiore di un errore di compilazione. Per evitare tali situazioni, il compilatore ci dà un errore:
// ошибка компиляции
List<Object> objects = strings;
...e gli ricorda che List<String>non è un erede List<Object>. Questa è una regola ferrea per il funzionamento dei farmaci generici e deve essere ricordata quando li si utilizza. Andiamo avanti. Diciamo che abbiamo 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()");
   }
}
A capo della gerarchia ci sono semplicemente gli Animali: gli animali domestici vengono ereditati da loro. Gli animali domestici sono divisi in 2 tipologie: cani e gatti. Ora immaginiamo di dover creare un metodo semplice iterateAnimals(). Il metodo dovrebbe accettare una raccolta di qualsiasi animale ( Animal, Pet, Cat, Dog), scorrere tutti gli elementi e restituire ogni volta qualcosa alla console. Proviamo a scrivere questo metodo:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Sembrerebbe che il problema sia risolto! Tuttavia, come abbiamo scoperto di recente, List<Cat>o List<Dog>non List<Pet>siamo eredi List<Animal>! Pertanto, quando proviamo a chiamare un metodo iterateAnimals()con una lista di gatti, riceveremo un errore del compilatore:
import java.util.*;

public class Main3 {


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

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

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

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
La situazione non si presenta bene per noi! Si scopre che dovremo scrivere metodi separati per enumerare tutti i tipi di animali? In realtà no, non è necessario :) E i caratteri jolly ci aiuteranno in questo ! Risolveremo il problema con un metodo semplice, utilizzando la seguente costruzione:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Questo è il jolly. Più precisamente, questo è il primo di diversi tipi di caratteri jolly: " estende " (un altro nome è caratteri jolly con limite superiore ). Cosa ci dice questo disegno? Ciò significa che il metodo prende come input una raccolta di oggetti di classe Animalo oggetti di qualsiasi classe discendente Animal (? extends Animal). AnimalIn altre parole, il metodo può accettare la collection , Pet, Dogo come input Cat: non fa alcuna differenza. Assicuriamoci che funzioni:
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:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Abbiamo creato un totale di 4 raccolte e 8 oggetti e ci sono esattamente 8 voci nella console. Funziona tutto alla grande! :) I caratteri jolly ci hanno permesso di adattare facilmente la logica necessaria con l'associazione a tipi specifici in un unico metodo. Abbiamo eliminato la necessità di scrivere un metodo separato per ciascun tipo di animale. Immagina quanti metodi avremmo se la nostra applicazione fosse utilizzata in uno zoo o in una clinica veterinaria :) Ora diamo un'occhiata a una situazione diversa. La nostra gerarchia di ereditarietà rimarrà la stessa: la classe di livello più alto è Animal, appena sotto c'è la classe Pets Pete al livello successivo c'è Cate Dog. Ora è necessario riscrivere il metodo iretateAnimals()in modo che possa funzionare con qualsiasi tipo di animale tranne i cani . Collection<Animal>Cioè, dovrebbe accettare , Collection<Pet>o come input Collection<Cat>, ma non dovrebbe funzionare con Collection<Dog>. Come possiamo raggiungere questo risultato? Sembra che la prospettiva di scrivere un metodo separato per ciascun tipo si profila nuovamente davanti a noi :/ In quale altro modo possiamo spiegare la nostra logica al compilatore? E questo può essere fatto in modo molto semplice! Anche qui i jolly verranno in nostro aiuto. Ma questa volta useremo un tipo diverso: “ super ” (un altro nome è Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Anche qui il principio è simile. La costruzione <? super Cat>dice al compilatore che il metodo iterateAnimals()può prendere come input una collezione di oggetti della classe Cato qualsiasi altra classe antenata Cat. CatNel nostro caso, la classe stessa , il suo antenato - Petse l'antenato dell'antenato - si adattano a questa descrizione Animal. La classe Dognon rientra in questo vincolo e pertanto il tentativo di utilizzare un metodo con un elenco List<Dog>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);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Il nostro problema è risolto e ancora una volta i caratteri jolly si sono rivelati estremamente utili :) Questo conclude la lezione. Ora vedi quanto sia importante l'argomento dei farmaci generici quando si impara Java: ci abbiamo dedicato 4 lezioni! Ma ora hai una buona conoscenza dell'argomento e potrai metterti alla prova durante il colloquio :) E ora è il momento di tornare ai compiti! Buona fortuna per i tuoi studi. :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION