CodeGym/Blog Java/Aleatoriu/Wildcards în generice
John Squirrels
Nivel
San Francisco

Wildcards în generice

Publicat în grup
Bună! Să continuăm studiul nostru despre generice. Ați dobândit deja un corp substanțial de cunoștințe despre ele din lecțiile anterioare (despre utilizarea varargs atunci când lucrați cu generice și despre ștergerea tipului ), dar există un subiect important pe care nu l-am luat în considerare încă - metacaracterele . Aceasta este o caracteristică foarte importantă a genericelor. Atât de mult încât i-am dedicat o lecție separată! Acestea fiind spuse, nu este nimic deosebit de complicat în legătură cu metacaracterele. Veți vedea asta imediat :) Wildcards în generice - 1Să ne uităm la un exemplu:
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;
   }
}
Ce se petrece aici? Vedem două situații foarte asemănătoare. În acest caz, aruncăm un Stringobiect pe un Objectobiect. Nu există probleme aici - totul funcționează conform așteptărilor. Dar în a doua situație, compilatorul generează o eroare. Dar facem același lucru, nu-i așa? De data aceasta, pur și simplu folosim o colecție de mai multe obiecte. Dar de ce apare eroarea? Care este diferența? Aruncăm un Stringobiect pe unul Objectsau 20 de obiecte? Există o distincție importantă între un obiect și o colecție de obiecte . Dacă Bclasa este un copil al Aclasei, atunci Collection<B>nu este un copil al Collection<A>. Acesta este motivul pentru care nu am fost capabili să ne aruncăm List<String>la aList<Object>. Stringeste un copil al Object, dar List<String>nu este un copil al List<Object>. Acest lucru poate să nu pară super intuitiv. De ce creatorii limbii au făcut-o așa? Să ne imaginăm că compilatorul nu ne dă o eroare:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
În acest caz, am putea, de exemplu, să facem următoarele:
objects.add(new Object());
String s = strings.get(0);
Deoarece compilatorul nu ne-a dat nicio eroare și ne-a permis să creăm o List<Object>referință care indică spre strings, putem adăuga orice Objectobiect vechi la stringscolecție! Astfel, am pierdut garanția că colecția noastră conține doar obiectele Stringspecificate de argumentul tip în invocarea tipului generic . Cu alte cuvinte, am pierdut principalul avantaj al medicamentelor generice - siguranța tipului. Și pentru că compilatorul nu ne-a împiedicat să facem acest lucru, vom primi o eroare doar în timpul executării, care este întotdeauna mult mai gravă decât o eroare de compilare. Pentru a preveni astfel de situații, compilatorul ne dă o eroare:
// Compilation error
List<Object> objects = strings;
...și ne reamintește că List<String>nu este un descendent al List<Object>. Aceasta este o regulă fermă pentru generice și trebuie reținută atunci când lucrați cu ele. Sa trecem peste. Să presupunem că avem o ierarhie mică de clasă:
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()");
   }
}
Ierarhia este în frunte cu o clasă Animal simplă, care este moștenită de Pet. Animalul de companie are 2 subclase: câine și pisică. Acum să presupunem că trebuie să creăm o iterateAnimals()metodă simplă. Metoda ar trebui să ia o colecție de orice animale ( Animal, Pet, Cat, Dog), să itereze peste toate elementele și să afișeze un mesaj pe consolă în timpul fiecărei iterații. Să încercăm să scriem o astfel de metodă:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Se pare că problema este rezolvată! Cu toate acestea, după cum am aflat recent, List<Cat>și List<Dog>nu List<Pet>sunt descendenți ai List<Animal>! Aceasta înseamnă că atunci când încercăm să apelăm iterateAnimals()metoda cu o listă de pisici, obținem o eroare de compilare:
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);
   }
}
Situația nu arată prea bine pentru noi! Trebuie să scriem metode separate pentru enumerarea fiecărui fel de animal? De fapt, nu, noi nu :) Și după cum se întâmplă, metacaracterele ne ajută cu asta! Putem rezolva problema cu o metodă simplă folosind următorul construct:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Acesta este un wildcard. Mai precis, acesta este primul dintre mai multe tipuri de wildcards. Este cunoscut ca un caractere joker cu mărgini superioare și este exprimat prin ? se extinde . Ce ne spune acest construct? Aceasta înseamnă că metoda acceptă o colecție de Animalobiecte sau o colecție de obiecte din orice clasă care descinde din Animal(? extinde Animal). Cu alte cuvinte, metoda poate accepta o colecție de Animal, Pet, Dog, sau Catobiecte - nu are nicio diferență. Să ne convingem că funcționează:
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);
}
Ieșire din consolă:
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!
Am creat un total de 4 colecții și 8 obiecte, iar pe consolă sunt exact 8 intrări. Totul funcționează grozav! :) Wildcard-ul ne-a permis să potrivim cu ușurință logica necesară legată de anumite tipuri într-o singură metodă. Am eliminat necesitatea de a scrie o metodă separată pentru fiecare tip de animal. Imaginați-vă de câte metode am fi avut nevoie dacă aplicația noastră ar fi folosită de o grădină zoologică sau de un cabinet veterinar :) Dar acum să ne uităm la o situație diferită. Ierarhia noastră de moștenire rămâne neschimbată: clasa de nivel superior este Animal, cu Petclasa chiar dedesubt, iar clasele Catși Dogla nivelul următor. Acum trebuie să rescrieți iterateAnimals()metoda, astfel încât să funcționeze cu orice tip de animal, cu excepția câinilor . Adică ar trebui să accepte Collection<Animal>,Collection<Pet>sau Collection<Car>, dar nu ar trebui să funcționeze cu Collection<Dog>. Cum putem realiza acest lucru? Se pare că ne confruntăm din nou cu perspectiva de a scrie o metodă separată pentru fiecare tip :/ Cum altfel îi explicăm compilatorului ce vrem să se întâmple? De fapt, este destul de simplu! Încă o dată, wildcard-urile ne vin în ajutor aici. Dar de data aceasta vom folosi un alt tip de wildcard - un wildcard cu mărginire inferioară , care este exprimat folosind 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!");
   }
}
Aici principiul este similar. Construcția <? super Cat>îi spune compilatorului că iterateAnimals()metoda poate accepta ca intrare o colecție de Catobiecte sau orice strămoș al Catclasei ca intrare. În acest caz, Catclasa, părintele său Petși părintele părintelui său, Animaltoate se potrivesc cu această descriere. Clasa Dognu se potrivește cu restricția noastră, așa că o încercare de a utiliza metoda cu un List<Dog>argument va duce la o eroare de compilare:
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);
}
Ne-am rezolvat problema și, încă o dată, metacaracterele s-au dovedit a fi extrem de utile :) Cu asta, lecția a luat sfârșit. Acum vezi cât de importante sunt genericele în studiul tău despre Java - am avut 4 lecții întregi despre ele! Dar acum ești bine versat în subiect și îți poți dovedi abilitățile la interviurile de angajare :) Și acum, este timpul să revenim la sarcini! Cel mai bun succes în studiile tale! :)
Comentarii
  • Popular
  • Nou
  • Vechi
Trebuie să fii conectat pentru a lăsa un comentariu
Această pagină nu are încă niciun comentariu