Hi! Ipinagpapatuloy namin ang aming serye ng mga aralin sa generics. Nakakuha kami dati ng pangkalahatang ideya kung ano ang mga ito at kung bakit kailangan ang mga ito. Ngayon, matututunan natin ang higit pa tungkol sa ilan sa mga feature ng generics at tungkol sa pagtatrabaho sa kanila. Tara na! Uri ng bura - 1Sa huling aralin , pinag-usapan natin ang pagkakaiba sa pagitan ng mga generic na uri at mga hilaw na uri . Ang isang raw na uri ay isang generic na klase na ang uri ay inalis.

List list = new ArrayList();
Narito ang isang halimbawa. Dito hindi namin ipinapahiwatig kung anong uri ng mga bagay ang ilalagay sa aming List. Kung susubukan naming gumawa ng ganoon Listat magdagdag ng ilang 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 Java 5. Sa oras na ang bersyon na ito ay inilabas, ang mga programmer ay nagsulat na ng isang grupo ng mga code gamit ang mga hilaw na uri, kaya ang tampok na ito ng wika ay hindi tumigil sa paggana, at ang kakayahang lumikha ng mga hilaw na uri sa Java ay napanatili. Gayunpaman, ang problema ay naging mas malawak. Tulad ng alam mo, ang Java code ay na-convert sa isang espesyal na pinagsama-samang format na tinatawag na bytecode, na pagkatapos ay isinasagawa ng Java virtual machine. Ngunit kung maglalagay kami ng impormasyon tungkol sa mga parameter ng uri sa bytecode sa panahon ng proseso ng conversion, masisira nito ang lahat ng dating nakasulat na code, dahil walang mga parameter ng uri bago ang Java 5! Kapag nagtatrabaho sa generics, mayroong isang napakahalagang konsepto na kailangan mong tandaan. Tinatawag itong type erasure. Nangangahulugan ito na ang isang klase ay walang impormasyon tungkol sa isang uri ng parameter. Available lang ang impormasyong ito sa panahon ng compilation at mabubura (naging hindi naa-access) bago ang runtime. Kung susubukan mong ilagay ang maling uri ng bagay sa iyong List<String>, bubuo ng error ang compiler. Ito mismo ang gustong makamit ng mga tagalikha ng wika noong gumawa sila ng mga generic: mga pagsusuri sa oras ng pag-compile. Ngunit kapag ang lahat ng iyong Java code ay naging bytecode, hindi na ito naglalaman ng impormasyon tungkol sa mga parameter ng uri. Sa bytecode, ang iyong List<Cat>listahan ng mga pusa ay hindi naiiba sa List<String>mga string. Sa bytecode, walang nagsasabing iyon catsay isang listahan ng Catmga bagay. Ang nasabing impormasyon ay nabubura sa panahon ng compilation — tanging ang katotohanang mayroon kang List<Object> catslistahan ang mapupunta sa bytecode ng program. 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 TestClassna klase. Ito ay medyo simple: ito ay talagang isang maliit na "koleksyon" ng 2 mga bagay, na agad na nakaimbak kapag ang bagay ay nilikha. Mayroon itong 2 Tfield. Kapag ang createAndAdd2Values()pamamaraan ay naisakatuparan, ang dalawang nakapasa na mga bagay ( Object aat Object bdapat na i-cast sa Turi at pagkatapos ay idagdag sa TestClassbagay. Sa main()pamamaraan, lumikha kami ng isang TestClass<Integer>, ibig sabihin, ang Integeruri ng argumento ay pumapalit sa Integeruri ng parameter. Kami ay nagpapasa din ng a Doubleat a Stringsa ang createAndAdd2Values()pamamaraan. Sa tingin mo ba ay gagana ang aming programa? Pagkatapos ng lahat, tinukoy namin Integerbilang ang uri ng argumento, ngunit ang isang Stringtiyak na hindi maaaring i-cast sa isang Integer! Patakbuhin natin angmain()pamamaraan at tseke. Output ng console:

22.111 
Test String
Iyon ay hindi inaasahan! Bakit nangyari ito? Ito ay resulta ng uri ng pagbura. Ang impormasyon tungkol sa Integeruri ng argumento na ginamit upang i-instantiate ang aming TestClass<Integer> testobject ay nabura nang ang code ay pinagsama-sama. Ang patlang ay nagiging TestClass<Object> test. Ang aming Doubleat Stringmga argumento ay madaling na-convert sa Objectmga bagay (hindi sila na-convert sa Integermga bagay tulad ng inaasahan namin!) at tahimik na idinagdag sa TestClass. Narito ang isa pang simple ngunit napakahayag na halimbawa ng pagbubura ng uri:

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 mga argumento — String, Integer, at sa sarili naming Catklase. Ngunit sa panahon ng pag-convert sa bytecode, lahat ng tatlong listahan ay nagiging List<Object>, kaya kapag tumakbo ang programa, sinasabi nito sa amin na ginagamit namin ang parehong klase sa lahat ng tatlong kaso.

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

Mayroong napakahalagang punto na dapat na malinaw na maunawaan kapag nagtatrabaho sa mga array at generic na klase (tulad ng List). Dapat mo ring isaalang-alang ito kapag pumipili ng mga istruktura ng data para sa iyong programa. Ang mga generic ay napapailalim sa pagbubura ng uri. Ang impormasyon tungkol sa mga parameter ng uri ay hindi magagamit sa runtime. Sa kabaligtaran, alam ng mga array ang tungkol sa at maaaring gumamit ng impormasyon tungkol sa kanilang uri ng data kapag tumatakbo ang program. Ang pagtatangkang maglagay ng di-wastong uri sa isang array ay magdudulot ng 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, maaaring may mga isyu sa compatibility ang mga ito. Higit sa lahat, hindi ka makakagawa ng array ng mga generic na bagay o kahit na isang parameterized array lang. Medyo nakakalito ba iyon? Tignan natin. 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 List<String>mga bagay, makakakuha kami ng isang error sa compilation na nagrereklamo tungkol sa paggawa ng generic na array:

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];
   }
}
Ngunit bakit ito ginagawa? Bakit hindi pinapayagan ang paglikha ng mga naturang array? Ang lahat ng ito ay upang magbigay ng uri ng kaligtasan. Kung hahayaan tayo ng compiler na lumikha ng mga ganitong array ng mga generic na bagay, maaari tayong gumawa ng isang toneladang problema para sa ating sarili. 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 ang paggawa ng array like List<String>[] stringListsay pinapayagan at hindi bubuo ng compilation error. Kung totoo ito, narito ang ilang bagay na maaari naming gawin: Sa linya 1, gumawa kami ng hanay ng mga listahan: List<String>[] stringLists. Ang aming array ay naglalaman ng isa List<String>. Sa linya 2, gumawa kami ng listahan ng mga numero: List<Integer>. Sa linya 3, itinalaga namin ang aming List<String>[]sa isang Object[] objectsvariable. Pinapayagan ito ng wikang Java: ang isang hanay ng Xmga bagay ay maaaring mag-imbak Xng mga bagay at mga bagay ng lahat ng mga subclass X. Alinsunod dito, maaari mong ilagay ang anumang bagay sa isang Objectarray. Sa linya 4, pinapalitan namin ang nag-iisang elemento ng objects()array (a List<String>) ng isang List<Integer>. Kaya, naglalagay kami List<Integer>ng isang array na inilaan lamang upang mag-imbakList<String>mga bagay! Makaka-encounter lang kami ng error kapag nag-execute kami ng line 5. ClassCastExceptionItatapon ang A sa runtime. Alinsunod dito, ang isang pagbabawal sa paglikha ng naturang mga array ay idinagdag sa Java. Hinahayaan tayo nitong maiwasan ang mga ganitong sitwasyon.

Paano ako makakalagpas sa type erasure?

Well, natutunan namin ang tungkol sa type erasure. Subukan nating linlangin ang sistema! :) Gawain: Mayroon tayong generic TestClass<T>na klase. Nais naming magsulat ng isang createNewT()paraan para sa klase na ito na lilikha at magbabalik ng bagong Tbagay. Ngunit ito ay imposible, tama ba? Ang lahat ng impormasyon tungkol sa Turi ay nabubura sa panahon ng compilation, at sa runtime hindi namin matukoy kung anong uri ng object ang kailangan naming gawin. Mayroong talagang isang nakakalito na paraan upang gawin ito. Malamang naaalala mo na may Classklase ang Java. Magagamit namin ito upang matukoy ang klase ng alinman sa aming 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 aspeto na hindi natin napag-usapan. Sa dokumentasyon ng Oracle, makikita mo na generic ang klase ng Class! Uri ng pagbura - 3

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

Sinasabi ng dokumentasyon, "T - ang uri ng klase na namodelo ng bagay na ito ng Klase." Sa pagsasalin nito mula sa wika ng dokumentasyon tungo sa simpleng pananalita, naiintindihan namin na ang klase ng Integer.classbagay ay hindi lamang Class, ngunit sa halip Class<Integer>. Ang uri ng String.classbagay ay hindi lang Class, kundi Class<String>, atbp. Kung hindi pa rin malinaw, subukang magdagdag ng uri ng parameter sa nakaraang halimbawa:

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;
   }
}
At ngayon, gamit ang kaalamang ito, maaari nating lampasan ang uri ng pagbura at magawa ang ating gawain! Subukan nating makakuha ng impormasyon tungkol sa isang uri ng parameter. Ang aming uri ng argumento ay magiging MySecretClass:

public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
At 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:

A MySecretClass object was created successfully!
Ipinasa lang namin ang kinakailangang argumento ng klase sa tagabuo ng aming generic na klase:

TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Nagpahintulot ito sa amin na i-save ang impormasyon tungkol sa uri ng argumento, na pinipigilan itong ganap na mabura. Bilang resulta, nakagawa kami ng isangTbagay! :) With that, natapos na ang lesson ngayon. Dapat mong laging tandaan ang uri ng erasure kapag nagtatrabaho sa generics. Mukhang hindi masyadong maginhawa ang workaround na ito, ngunit dapat mong maunawaan na ang mga generic ay hindi bahagi ng wikang Java noong ito ay ginawa. Ang feature na ito, na tumutulong sa amin na gumawa ng mga naka-parameter na koleksyon at mahuli ang mga error sa panahon ng compilation, ay na-tack sa ibang pagkakataon. Sa ilang iba pang mga wika na may kasamang mga generic mula sa unang bersyon, walang uri ng pagbura (halimbawa, sa C#). Nga pala, hindi pa tayo tapos mag-aral ng generics! Sa susunod na aralin, makikilala mo ang ilan pang tampok ng generics. Sa ngayon, mainam na lutasin ang ilang gawain! :)