JavaRush /Java Blog /Random-TL /Mga uri ng pagbura

Mga uri ng pagbura

Nai-publish sa grupo
Kamusta! Ipinagpapatuloy namin ang aming serye ng mga lektura sa generics. Noong nakaraan , nalaman namin sa pangkalahatang mga tuntunin kung ano ito at kung bakit ito kinakailangan. Ngayon ay pag-uusapan natin ang ilan sa mga tampok ng generics at titingnan ang ilang mga pitfalls kapag nagtatrabaho sa kanila. Go! Mga uri ng burahin - 1Sa huling lecture, pinag-usapan natin ang pagkakaiba ng Generic Types at Raw Types . Kung sakaling nakalimutan mo, ang Raw Type ay isang generic na klase kung saan inalis ang uri nito.
List list = new ArrayList();
Narito ang isang halimbawa. Dito hindi namin tinukoy kung anong uri ng mga bagay ang ilalagay sa aming List. Kung susubukan naming lumikha ng isa Listat magdagdag ng ilang mga bagay dito, makakakita kami ng babala sa IDEa:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
Ngunit napag-usapan din namin ang katotohanan na ang mga generic ay lumitaw lamang sa bersyon ng Java 5. Sa oras na ito ay inilabas, ang mga programmer ay nagsulat ng maraming code gamit ang Raw Types, at upang hindi ito tumigil sa paggana, ang kakayahang gumawa at magtrabaho kasama ang Raw Types sa Java ay napanatili. Gayunpaman, ang problemang ito ay naging mas malawak. Ang Java code, tulad ng alam mo, ay na-convert sa espesyal na bytecode, na pagkatapos ay isinasagawa ng Java virtual machine. At kung sa panahon ng proseso ng pagsasalin ay naglagay kami ng impormasyon tungkol sa mga uri ng parameter sa bytecode, masisira nito ang lahat ng dating nakasulat na code, dahil bago ang Java 5 ay walang mga uri ng parameter na umiiral! Kapag nagtatrabaho sa generics, mayroong isang napakahalagang tampok na kailangan mong tandaan. Tinatawag itong type erasure. Ang kakanyahan nito ay nakasalalay sa katotohanang walang impormasyon tungkol sa uri ng parameter nito na nakaimbak sa loob ng klase. Ang impormasyong ito ay makukuha lamang sa yugto ng compilation at nabubura (naging hindi naa-access) sa runtime. Kung susubukan mong maglagay ng object ng maling uri sa iyong List<String>, ang compiler ay maghahagis ng error. Ito mismo ang nakamit ng mga tagalikha ng wika sa pamamagitan ng paglikha ng mga generics - mga tseke sa yugto ng compilation. Ngunit kapag ang lahat ng Java code na iyong isinusulat ay naging bytecode, walang magiging impormasyon tungkol sa mga uri ng parameter. Sa loob ng bytecode, ang iyong listahan ng List<Cat>mga pusa ay hindi mag-iiba sa List<String>mga string. Wala sa bytecode ang magsasabi na catsito ay isang listahan ng mga bagay Cat. Ang impormasyon tungkol dito ay mabubura sa panahon ng pagsasama-sama, at tanging ang impormasyon na mayroon kang partikular na listahan sa iyong programa ang mapupunta sa byte code List<Object> cats. Tingnan natin kung paano ito gumagana:
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();
   }
}
Gumawa kami ng sarili naming generic na klase TestClass. Ito ay medyo simple: mahalagang ito ay isang maliit na "koleksyon" ng 2 mga bagay, na inilalagay doon kaagad kapag ang bagay ay nilikha. Mayroon itong 2 bagay bilang mga patlang T. Kapag ang pamamaraan ay naisakatuparan, createAndAdd2Values()ang dalawang nakapasa na mga bagay ay dapat na mai-cast Object asa Object baming uri T, pagkatapos nito ay idadagdag ang mga ito sa bagay TestClass. Sa paraan main()na ating nilikha TestClass<Integer>, ibig sabihin, sa kalidad Tay magkakaroon tayo ng Integer. Ngunit sa parehong oras, createAndAdd2Values()ipinapasa namin ang isang numero Doubleat isang bagay sa pamamaraan String. Sa tingin mo ba gagana ang ating programa? Pagkatapos ng lahat, tinukoy namin bilang isang uri ng parameter Integer, ngunit Stringtiyak na hindi ito mai-cast sa Integer! Patakbuhin natin ang pamamaraan main()at suriin. Output ng console: 22.111 Test String Hindi inaasahang resulta! Bakit nangyari ito? Sakto dahil sa type erasure. Sa panahon ng pag-compile ng code, ang impormasyon tungkol sa uri ng parameter Integerng aming bagay TestClass<Integer> testay nabura. Lumingon siya sa TestClass<Object> test. Ang aming mga parameter ay binago sa walang anumang mga problema Double( at hindi sa , gaya ng inaasahan namin!) at tahimik na idinagdag sa . Narito ang isa pang simple ngunit napaka-naglalarawan na halimbawa ng pagbubura ng uri: StringObjectIntegerTestClass
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());

   }
}
Output ng console: true true Mukhang gumawa kami ng mga koleksyon na may tatlong magkakaibang uri ng parameter - String, Integer, at ang klase na ginawa namin Cat. Ngunit sa panahon ng conversion sa bytecode, ang lahat ng tatlong listahan ay naging List<Object>, kaya kapag naisakatuparan, ang programa ay nagsasabi sa amin na sa lahat ng tatlong mga kaso ay gumagamit kami ng parehong klase.

I-type ang erasure kapag nagtatrabaho sa mga array at generics

Mayroong isang napakahalagang punto na dapat na malinaw na maunawaan kapag nagtatrabaho sa mga array at generics (halimbawa, List). Ito rin ay nagkakahalaga ng pagsasaalang-alang kapag pumipili ng istraktura ng data para sa iyong programa. Ang mga generic ay napapailalim sa pagbubura ng uri. Ang impormasyon tungkol sa uri ng parameter ay hindi magagamit sa panahon ng pagpapatupad ng programa. Sa kaibahan, ang mga array ay nakakaalam at maaaring gumamit ng impormasyon tungkol sa kanilang uri ng data sa panahon ng pagpapatupad ng programa. Ang pagsisikap na maglagay ng isang halaga ng maling uri sa isang array ay magtapon ng isang pagbubukod:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Output ng console:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Dahil may napakalaking pagkakaiba sa pagitan ng mga array at generic, maaari silang magkaroon ng mga isyu sa compatibility. Una sa lahat, hindi ka makakagawa ng hanay ng mga generic na bagay o kahit isang naka-type na array lang. Parang medyo nakakalito? Tingnan natin nang maigi. Halimbawa, hindi mo magagawa ang alinman sa mga ito sa Java:
new List<T>[]
new List<String>[]
new T[]
Kung susubukan naming lumikha ng isang hanay ng mga listahan List<String>, makakakuha kami ng isang generic na error sa paggawa ng array compilation:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Ngunit bakit ito ginawa? Bakit ipinagbabawal ang paglikha ng gayong mga array? Ito ay lahat upang matiyak ang kaligtasan ng uri. Kung pinahintulutan tayo ng compiler na lumikha ng mga ganitong array mula sa mga generic na bagay, maaari tayong magkaroon ng maraming problema. Narito ang isang simpleng halimbawa mula sa aklat ni Joshua Bloch na “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)
}
Isipin natin na List<String>[] stringListspapayagan ang paggawa ng array, at hindi magrereklamo ang compiler. Narito ang maaari naming gawin sa kasong ito: Sa linya 1, lumikha kami ng hanay ng mga sheet List<String>[] stringLists. Ang aming array ay naglalaman ng isa List<String>. Sa linya 2 lumikha kami ng isang listahan ng mga numero List<Integer>. Sa linya 3 itinalaga namin ang aming array List<String>[]sa isang variable Object[] objects. Pinapayagan ka ng wikang Java na gawin ito: Xmaaari mong ilagay ang parehong mga bagay Xat mga bagay ng lahat ng mga klase ng bata sa isang hanay ng mga bagay Х. Alinsunod dito, Objectsmaaari mong ilagay ang anumang bagay sa array. Sa linya 4 pinapalitan namin ang nag-iisang elemento ng array objects (List<String>)na may isang listahan List<Integer>. Bilang resulta, inilagay namin List<Integer>sa aming array, na inilaan lamang para sa pag-iimbak List<String>! Makakatagpo lamang kami ng isang error kapag ang code ay umabot sa linya 5. Isang pagbubukod ang itatapon sa panahon ng pagpapatupad ng programa ClassCastException. Samakatuwid, ang pagbabawal sa paglikha ng gayong mga array ay ipinakilala sa wikang Java - ito ay nagpapahintulot sa amin na maiwasan ang mga ganitong sitwasyon.

Paano ko ma-bypass ang type erasure?

Well, natutunan namin ang tungkol sa type erasure. Subukan nating dayain ang sistema! :) Gawain: Mayroon kaming generic class TestClass<T>. Kailangan nating lumikha ng isang paraan dito createNewT()na lilikha at magbabalik ng bagong bagay na may uri Т. Ngunit imposibleng gawin ito, tama ba? Ang lahat ng impormasyon tungkol sa uri Тay mabubura sa panahon ng compilation, at habang tumatakbo ang program, hindi namin malalaman kung anong uri ng bagay ang kailangan naming gawin. Sa katunayan, mayroong isang nakakalito na paraan. Malamang naaalala mo na mayroong isang klase sa Java Class. Gamit ito, makukuha natin ang klase ng alinman sa ating mga bagay:
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);
   }
}
Output ng console:

class java.lang.Integer
class java.lang.String
Ngunit narito ang isang tampok na hindi namin pinag-usapan. Sa dokumentasyon ng Oracle makikita mo na ang Class ay isang generic na klase! Mga uri ng burahin - 3Ang dokumentasyon ay nagsasabing: "Ang T ay ang uri ng klase na na-modelo ng Class object na ito." Kung isasalin natin ito mula sa wikang dokumentasyon sa wika ng tao, nangangahulugan ito na ang klase para sa isang bagay Integer.classay hindi lamang Class, ngunit Class<Integer>. Ang uri ng isang bagay string.classay hindi lamang Class, Class<String>, atbp. Kung hindi pa rin malinaw, subukang magdagdag ng parameter ng uri sa nakaraang halimbawa:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
At ngayon, gamit ang kaalamang ito, maaari nating lampasan ang uri ng pagbura at lutasin ang ating problema! Subukan nating makakuha ng impormasyon tungkol sa uri ng parameter. Ang papel nito ay gagampanan ng klase MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
Narito kung paano namin ginagamit ang aming solusyon sa pagsasanay:
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();

   }
}
Output ng console:

Объект секретного класса успешно создан!
Ipinasa lang namin ang kinakailangang parameter ng klase sa constructor ng aming generic na klase:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Salamat dito, nag-save kami ng impormasyon tungkol sa uri ng parameter at pinrotektahan namin ito mula sa pagkabura. Bilang resulta, nakagawa kami ng object T! :) Ito ang nagtatapos sa lecture ngayon. Ang pagbubura ng uri ay palaging isang bagay na dapat tandaan kapag nagtatrabaho sa mga generic. Mukhang hindi ito masyadong maginhawa, ngunit kailangan mong maunawaan na ang mga generic ay hindi bahagi ng wikang Java noong ito ay nilikha. Ito ay isang idinagdag na feature sa ibang pagkakataon na tumutulong sa amin na gumawa ng mga na-type na koleksyon at makakuha ng mga error sa yugto ng pagsasama-sama. Ang ilang iba pang mga wika kung saan umiral ang mga generic mula noong bersyon 1 ay walang uri ng erasure (halimbawa, C#). Gayunpaman, hindi pa tayo tapos sa pag-aaral ng generics! Sa susunod na panayam ay makikilala mo ang ilang higit pang mga tampok ng pagtatrabaho sa kanila. Pansamantala, ito ay magiging maganda upang malutas ang ilang mga problema! :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION