CodeGym /Blog Java /Aleatoriu /Ștergere tip
John Squirrels
Nivel
San Francisco

Ștergere tip

Publicat în grup
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! Ștergere tip - 1Î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.

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ă catseste o listă de Catobiecte. Astfel de informații sunt șterse în timpul compilării - doar faptul că aveți o List<Object> catslistă 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ă TestClassclasă 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 Tcampuri. Când createAndAdd2Values()metoda este executată, cele două obiecte transmise ( Object ași Object btrebuie să fie turnate la Ttip și apoi adăugate la TestClassobiect. În main()metodă, creăm un TestClass<Integer>, adică Integerargumentul tip înlocuiește Integerparametrul tip. De asemenea, trecem a Doubleși a Stringla metoda createAndAdd2Values(). Crezi că programul nostru va funcționa? La urma urmei, am specificat Integerca argument de tip, dar cu Stringsiguranță 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 Integerargumentul tip folosit pentru a instanția obiectul nostru TestClass<Integer> testau fost șterse atunci când codul a fost compilat. Câmpul devine TestClass<Object> test. Argumentele noastre Doubleși Stringau fost ușor convertite în Objectobiecte (nu sunt convertite în Integerobiecte 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ă Catclasă. 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 fi List). 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>[] stringListseste 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[] objectsvariabile. Limbajul Java permite acest lucru: o matrice de Xobiecte poate stoca Xobiecte și obiecte din toate subclasele X. În consecință, puteți pune orice într-o Objectmatrice. Î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 ClassCastExceptionva 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 o TestClass<T>clasă generică. Vrem să scriem o createNewT()metodă pentru această clasă care va crea și returna un nou Tobiect. Dar acest lucru este imposibil, nu? Toate informațiile despre Ttip 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 Classclasă. Î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ă! Ștergere tip - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

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 Integer.classnu este doar Class, ci mai degrabă Class<Integer>. Tipul obiectului String.classnu 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 unTobiect! :) 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! :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION