Bună! Continuăm seria noastră de lecții despre generice. Am avut anterior o idee generală despre ce sunt acestea și de ce sunt necesare. Astăzi vom afla mai multe despre unele dintre caracteristicile genericelor și despre lucrul cu acestea. Să mergem!
În ultima lecție , am vorbit despre diferența dintre tipurile generice și tipurile brute . Un tip brut este o clasă generică al cărei tip a fost eliminat.
Documentația spune: „T - tipul clasei modelate de acest obiect Class”. Traducând acest lucru din limbajul documentării în vorbire simplă, înțelegem că clasa obiectului

List list = new ArrayList();
Iată un exemplu. Aici nu indicăm ce tip de obiecte vor fi plasate în List
. Dacă încercăm să creăm un astfel de List
și să îi adăugăm câteva obiecte, vom vedea un avertisment în IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Dar am vorbit și despre faptul că genericele au apărut doar în Java 5. Până la lansarea acestei versiuni, programatorii scriseseră deja o grămadă de coduri folosind tipuri brute, astfel încât această caracteristică a limbajului nu s-a putut opri din funcționare, iar capacitatea de a Creați tipuri brute în Java a fost păstrat. Cu toate acestea, problema s-a dovedit a fi mai răspândită. După cum știți, codul Java este convertit într-un format special compilat numit bytecode, care este apoi executat de mașina virtuală Java. Dar dacă punem informații despre parametrii de tip în bytecode în timpul procesului de conversie, s-ar rupe tot codul scris anterior, deoarece nu existau parametri de tip înainte de Java 5! Când lucrați cu medicamente generice, există un concept foarte important pe care trebuie să-l amintiți. Se numește ștergere de tip. Înseamnă că o clasă nu conține informații despre un parametru de tip. Aceste informații sunt disponibile numai în timpul compilării și sunt șterse (devin inaccesibile) înainte de rulare. Dacă încercați să puneți tipul greșit de obiect în List<String>
, compilatorul va genera o eroare. Acesta este exact ceea ce doresc să obțină creatorii limbii atunci când au creat generice: verificări în timp de compilare. Dar când tot codul tău Java se transformă în bytecode, acesta nu mai conține informații despre parametrii de tip. În bytecode, List<Cat>
lista de pisici nu este diferită de List<String>
șiruri. În bytecode, nimic nu spune că cats
este o listă de Cat
obiecte. Astfel de informații sunt șterse în timpul compilării - doar faptul că aveți o List<Object> cats
listă va ajunge în codul de octeți al programului. Să vedem cum funcționează:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
Am creat propria noastră TestClass
clasă generică. Este destul de simplu: este de fapt o mică „colecție” de 2 obiecte, care sunt stocate imediat când obiectul este creat. Are 2 T
campuri. Când createAndAdd2Values()
metoda este executată, cele două obiecte transmise ( Object a
și Object b
trebuie să fie turnate la T
tip și apoi adăugate la TestClass
obiect. În main()
metodă, creăm un TestClass<Integer>
, adică Integer
argumentul tip înlocuiește Integer
parametrul tip. De asemenea, trecem a Double
și a String
la metoda createAndAdd2Values()
. Crezi că programul nostru va funcționa? La urma urmei, am specificat Integer
ca argument de tip, dar cu String
siguranță nu poate fi turnat la un Integer
! Să rulămmain()
metoda si verificarea. Ieșire din consolă:
22.111
Test String
A fost neașteptat! De ce s-a întâmplat asta? Este rezultatul ștergerii tipului. Informațiile despre Integer
argumentul tip folosit pentru a instanția obiectul nostru TestClass<Integer> test
au fost șterse atunci când codul a fost compilat. Câmpul devine TestClass<Object> test
. Argumentele noastre Double
și String
au fost ușor convertite în Object
obiecte (nu sunt convertite în Integer
obiecte așa cum ne așteptam!) și adăugate în liniște la TestClass
. Iată un alt exemplu simplu, dar foarte revelator de ștergere a tipului:
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
Ieșire din consolă:
true
true
Se pare că am creat colecții cu trei tipuri diferite de argumente — String
, Integer
și propria noastră Cat
clasă. Dar în timpul conversiei în bytecode, toate cele trei liste devin List<Object>
, așa că atunci când programul rulează, ne spune că folosim aceeași clasă în toate cele trei cazuri.
Tastați ștergere atunci când lucrați cu matrice și generice
Există un punct foarte important care trebuie înțeles clar atunci când lucrați cu matrice și clase generice (cum ar fiList
). De asemenea, ar trebui să luați în considerare acest lucru atunci când alegeți structurile de date pentru programul dvs. Genericurile sunt supuse ștergerii tipului. Informațiile despre parametrii de tip nu sunt disponibile în timpul execuției. În schimb, matricele cunosc și pot folosi informații despre tipul lor de date atunci când programul rulează. Încercarea de a introduce un tip nevalid într-o matrice va determina lansarea unei excepții:
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
Ieșire din consolă:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Deoarece există o diferență atât de mare între matrice și generice, acestea ar putea avea probleme de compatibilitate. Mai presus de toate, nu puteți crea o matrice de obiecte generice sau chiar doar o matrice parametrizată. Sună puțin confuz? Hai să aruncăm o privire. De exemplu, nu puteți face nimic din toate acestea în Java:
new List<T>[]
new List<String>[]
new T[]
Dacă încercăm să creăm o matrice de List<String>
obiecte, obținem o eroare de compilare care se plânge de crearea matricei generice:
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
Dar de ce se face asta? De ce nu este permisă crearea unor astfel de matrice? Toate acestea pentru a oferi siguranță tipului. Dacă compilatorul ne-ar lăsa să creăm astfel de matrice de obiecte generice, ne-am putea face o mulțime de probleme pentru noi înșine. Iată un exemplu simplu din cartea lui Joshua Bloch „Effective Java”:
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
Să ne imaginăm că crearea unui tablou similar List<String>[] stringLists
este permisă și nu va genera o eroare de compilare. Dacă acest lucru ar fi adevărat, iată câteva lucruri pe care le-am putea face: În linia 1, creăm o serie de liste: List<String>[] stringLists
. Matricea noastră conține unul List<String>
. În rândul 2, creăm o listă de numere: List<Integer>
. În rândul 3, atribuim nostru List<String>[]
unei Object[] objects
variabile. Limbajul Java permite acest lucru: o matrice de X
obiecte poate stoca X
obiecte și obiecte din toate subclasele X
. În consecință, puteți pune orice într-o Object
matrice. În linia 4, înlocuim singurul element al objects()
matricei (a List<String>
) cu un List<Integer>
. Astfel, am pus un List<Integer>
într-o matrice care a fost destinat doar să stochezeList<String>
obiecte! Vom întâlni o eroare doar când executăm linia 5. A ClassCastException
va fi aruncat în timpul execuției. În consecință, în Java a fost adăugată o interdicție privind crearea unor astfel de matrice. Acest lucru ne permite să evităm astfel de situații.
Cum pot evita ștergerea tipului?
Ei bine, am aflat despre ștergerea tipului. Să încercăm să păcălim sistemul! :) Sarcină: Avem oTestClass<T>
clasă generică. Vrem să scriem o createNewT()
metodă pentru această clasă care va crea și returna un nou T
obiect. Dar acest lucru este imposibil, nu? Toate informațiile despre T
tip sunt șterse în timpul compilării, iar în timpul execuției nu putem determina ce tip de obiect trebuie să creăm. Există de fapt o modalitate dificilă de a face acest lucru. Probabil vă amintiți că Java are o Class
clasă. Îl putem folosi pentru a determina clasa oricăruia dintre obiectele noastre:
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
Ieșire din consolă:
class java.lang.Integer
class java.lang.String
Dar iată un aspect despre care nu am vorbit. În documentația Oracle, veți vedea că clasa Class este generică! 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
nu este doar Class
, ci mai degrabă Class<Integer>
. Tipul obiectului String.class
nu este doar Class
, ci mai degrabă Class<String>
, etc. Dacă încă nu este clar, încercați să adăugați un parametru de tip la exemplul anterior:
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
Și acum, folosind aceste cunoștințe, putem ocoli ștergerea tipului și ne putem îndeplini sarcina! Să încercăm să obținem informații despre un parametru de tip. Tipul nostru de argument va fi MySecretClass
:
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
Și iată cum folosim soluția noastră în practică:
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
Ieșire din consolă:
A MySecretClass object was created successfully!
Tocmai am transmis argumentul de clasă necesar constructorului clasei noastre generice:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Acest lucru ne-a permis să salvăm informațiile despre argumentul tip, împiedicând ștergerea acestuia în întregime. Drept urmare, am reușit să creăm unT
obiect! :) Cu asta, lecția de azi se încheie. Trebuie să vă amintiți întotdeauna ștergerea tipului atunci când lucrați cu generice. Această soluție nu pare foarte convenabilă, dar ar trebui să înțelegeți că genericele nu făceau parte din limbajul Java când a fost creat. Această caracteristică, care ne ajută să creăm colecții parametrizate și să detectăm erori în timpul compilării, a fost activată mai târziu. În alte limbi care au inclus generice din prima versiune, nu există ștergere de tip (de exemplu, în C#). Apropo, nu am terminat de studiat genericele! În lecția următoare, vă veți familiariza cu alte câteva caracteristici ale genericelor. Deocamdată, ar fi bine să rezolvi câteva sarcini! :)
GO TO FULL VERSION