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 :)
Să 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 String
obiect pe un Object
obiect. 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 String
obiect pe unul Object
sau 20 de obiecte? Există o distincție importantă între un obiect și o colecție de obiecte . Dacă B
clasa este un copil al A
clasei, 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>
. String
este 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 Object
obiect vechi la strings
colecție! Astfel, am pierdut garanția că colecția noastră conține doar obiectele String
specificate 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 Animal
obiecte 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 Cat
obiecte - 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 Pet
clasa chiar dedesubt, iar clasele Cat
și Dog
la 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 Cat
obiecte sau orice strămoș al Cat
clasei ca intrare. În acest caz, Cat
clasa, părintele său Pet
și părintele părintelui său, Animal
toate se potrivesc cu această descriere. Clasa Dog
nu 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! :)
GO TO FULL VERSION