JavaRush /Java Blog /Random-TL /Ang teorya ng generics sa Java o kung paano isagawa ang m...

Ang teorya ng generics sa Java o kung paano isagawa ang mga panaklong

Nai-publish sa grupo

Panimula

Simula sa JSE 5.0, idinagdag ang mga generic sa arsenal ng wikang Java.
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 1

Ano ang mga generic sa Java?

Ang mga generics (generalizations) ay mga espesyal na paraan ng wikang Java para sa pagpapatupad ng pangkalahatang programming: isang espesyal na diskarte sa paglalarawan ng data at mga algorithm na nagbibigay-daan sa iyo upang gumana sa iba't ibang uri ng data nang hindi binabago ang kanilang paglalarawan. Sa website ng Oracle, ang isang hiwalay na tutorial ay nakatuon sa mga generic: " Lesson: Generics ".

Una, upang maunawaan ang mga generic, kailangan mong maunawaan kung bakit kailangan ang mga ito at kung ano ang ibinibigay ng mga ito. Sa tutorial sa seksyong " Bakit Gumamit ng Generics ?" Sinasabi na ang isa sa mga layunin ay ang mas malakas na pag-compile-time type checking at pag-aalis ng pangangailangan para sa tahasang paghahagis.
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 2
Ihanda natin ang aming paboritong tutorialspoint online java compiler para sa mga eksperimento . Isipin natin ang code na ito:
import java.util.*;
public class HelloWorld{
	public static void main(String []args){
		List list = new ArrayList();
		list.add("Hello");
		String text = list.get(0) + ", world!";
		System.out.print(text);
	}
}
Ang code na ito ay gagana nang maayos. Ngunit paano kung pumunta sila sa amin at sinabi ang pariralang "Hello, mundo!" binugbog tapos babalik lang Hello? Alisin natin ang pagkakadugtong sa string mula sa code ", world!". Mukhang ano ang maaaring maging mas hindi nakakapinsala? Ngunit sa katunayan, makakatanggap kami ng isang error SA PANAHON NG COMPILATION : error: incompatible types: Object cannot be converted to String Ang bagay ay na sa aming kaso List ay nag-iimbak ng isang listahan ng mga bagay na may uri ng Bagay. Dahil ang String ay isang inapo ng Object (dahil ang lahat ng mga klase ay tahasang minana mula sa Object sa Java), nangangailangan ito ng isang tahasang cast, na hindi namin ginawa. At kapag pinagsama-sama, ang static na pamamaraan na String.valueOf(obj) ay tatawagin sa object, na sa huli ay tatawag sa toString method sa Object. Iyon ay, ang aming Listahan ay naglalaman ng Bagay. Lumalabas na kung saan kailangan namin ng isang partikular na uri, at hindi Bagay, kakailanganin naming gawin ang uri ng paghahagis sa aming sarili:
import java.util.*;
public class HelloWorld{
	public static void main(String []args){
		List list = new ArrayList();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println((String)str);
		}
	}
}
Gayunpaman, sa kasong ito, dahil Ang listahan ay tumatanggap ng isang listahan ng mga bagay, nag-iimbak hindi lamang ng String, kundi pati na rin ng Integer. Ngunit ang pinakamasamang bagay ay sa kasong ito ang tagatala ay hindi makakakita ng anumang mali. At dito tayo makakatanggap ng error SA PANAHON NG PAGPAPATAKBO (sinasabi rin nila na ang error ay natanggap "sa Runtime"). Ang magiging error ay: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Sumasang-ayon, hindi ang pinaka-kaaya-aya. At lahat ng ito ay dahil ang compiler ay hindi artificial intelligence at hindi nito mahulaan ang lahat ng ibig sabihin ng programmer. Upang sabihin sa compiler ang higit pa tungkol sa kung anong mga uri ang aming gagamitin, ipinakilala ng Java SE 5 ang mga generics . Itama natin ang ating bersyon sa pamamagitan ng pagsasabi sa compiler kung ano ang gusto natin:
import java.util.*;
public class HelloWorld {
	public static void main(String []args){
		List<String> list = new ArrayList<>();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println(str);
		}
	}
}
Tulad ng nakikita natin, hindi na natin kailangan ang cast sa String. Bilang karagdagan, mayroon na kaming mga anggulong bracket na nagbabalangkas ng mga generic. Ngayon ang compiler ay hindi papayagan ang klase na ma-compile hanggang sa alisin natin ang pagdaragdag ng 123 sa listahan, dahil ito ay Integer. Sasabihin niya sa atin. Tinatawag ng maraming tao ang generics na "syntactic sugar". At tama sila, dahil ang mga generic ay talagang magiging parehong mga kasta kapag pinagsama-sama. Tingnan natin ang bytecode ng mga pinagsama-samang klase: na may manu-manong pag-cast at paggamit ng mga generics:
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 3
Pagkatapos ng compilation, mabubura ang anumang impormasyon tungkol sa generics. Ito ay tinatawag na "Type Erasure" o " Type Erasure ". Idinisenyo ang type erasure at generics para magbigay ng backward compatibility sa mga mas lumang bersyon ng JDK, habang pinapayagan pa rin ang compiler na tumulong sa type inference sa mga mas bagong bersyon ng Java.
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 4

Mga Uri ng Hilaw o hilaw na uri

Kapag pinag-uusapan ang generics, palagi kaming may dalawang kategorya: mga typed type (Generic Types) at "raw" type (Raw Types). Ang mga hilaw na uri ay mga uri nang hindi tinutukoy ang "kwalipikasyon" sa mga anggulong bracket:
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 5
Ang mga na-type na uri ay ang kabaligtaran, na may indikasyon ng "paglilinaw":
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 6
Tulad ng nakikita natin, gumamit kami ng hindi pangkaraniwang disenyo, na minarkahan ng isang arrow sa screenshot. Ito ay isang espesyal na syntax na idinagdag sa Java SE 7, at ito ay tinatawag na " ang brilyante ", na nangangahulugang brilyante. Bakit? Maaari kang gumuhit ng pagkakatulad sa pagitan ng hugis ng isang brilyante at ang hugis ng mga kulot na brace: <> Ang diamante na syntax ay nauugnay din sa konsepto ng " Uri ng Hinuha ", o uri ng hinuha. Pagkatapos ng lahat, ang compiler, na nakikita ang <> sa kanan, ay tumitingin sa kaliwang bahagi, kung saan matatagpuan ang deklarasyon ng uri ng variable kung saan nakatalaga ang halaga. At mula sa bahaging ito ay nauunawaan niya kung anong uri ang nai-type ng halaga sa kanan. Sa katunayan, kung ang isang generic ay tinukoy sa kaliwang bahagi at hindi tinukoy sa kanang bahagi, ang compiler ay maaaring magpahiwatig ng uri:
import java.util.*;
public class HelloWorld{
	public static void main(String []args) {
		List<String> list = new ArrayList();
		list.add("Hello World");
		String data = list.get(0);
		System.out.println(data);
	}
}
Gayunpaman, ito ay magiging pinaghalong bagong istilo na may mga generic at lumang istilo kung wala ang mga ito. At ito ay lubhang hindi kanais-nais. Kapag kino-compile ang code sa itaas matatanggap namin ang mensahe: Note: HelloWorld.java uses unchecked or unsafe operations. Sa katunayan, tila hindi malinaw kung bakit kailangan nating magdagdag ng brilyante dito. Ngunit narito ang isang halimbawa:
import java.util.*;
public class HelloWorld{
	public static void main(String []args) {
		List<String> list = Arrays.asList("Hello", "World");
		List<Integer> data = new ArrayList(list);
		Integer intNumber = data.get(0);
		System.out.println(data);
	}
}
Tulad ng naaalala namin, ang ArrayList ay mayroon ding pangalawang constructor na kumukuha ng isang koleksyon bilang input. At dito nakasalalay ang daya. Kung walang syntax ng brilyante, hindi nauunawaan ng compiler na ito ay dinadaya, ngunit may brilyante ito. Samakatuwid, panuntunan #1 : palaging gumamit ng diamond syntax kung gumagamit kami ng mga uri ng type. Kung hindi, nanganganib kaming mawala kung saan kami gumagamit ng hilaw na uri. Upang maiwasan ang mga babala sa log na "gumagamit ng hindi naka-check o hindi ligtas na mga operasyon" maaari kang tumukoy ng isang espesyal na anotasyon sa paraan o klase na ginagamit: Ang @SuppressWarnings("unchecked") Suppress ay isinalin bilang suppress, ibig sabihin, literal, upang sugpuin ang mga babala. Ngunit isipin kung bakit mo naisipang ipahiwatig ito? Tandaan ang panuntunan bilang isa at marahil kailangan mong magdagdag ng pag-type.
Ang teorya ng generics sa Java o kung paano ilagay ang mga panaklong sa pagsasanay - 7

Mga Generic na Pamamaraan

Binibigyang-daan ka ng mga generic na mag-type ng mga pamamaraan. Mayroong isang hiwalay na seksyon na nakatuon sa tampok na ito sa Oracle tutorial: " Mga Generic na Paraan ". Mula sa tutorial na ito, mahalagang tandaan ang syntax:
  • may kasamang listahan ng mga na-type na parameter sa loob ng mga anggulong bracket;
  • ang listahan ng mga na-type na parameter ay napupunta bago ang ibinalik na paraan.
Tingnan natin ang isang halimbawa:
import java.util.*;
public class HelloWorld{

    public static class Util {
        public static <T> T getValue(Object obj, Class<T> clazz) {
            return (T) obj;
        }
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList("Author", "Book");
		for (Object element : list) {
		    String data = Util.getValue(element, String.class);
		    System.out.println(data);
		    System.out.println(Util.<String>getValue(element));
		}
    }
}
Kung titingnan mo ang klase ng Util, makikita natin ang dalawang naka-type na pamamaraan dito. Sa uri ng inference, maaari naming ibigay ang kahulugan ng uri nang direkta sa compiler, o maaari naming tukuyin ito sa aming sarili. Ang parehong mga pagpipilian ay ipinakita sa halimbawa. Sa pamamagitan ng paraan, ang syntax ay medyo lohikal kung iisipin mo ito. Kapag nagta-type ng isang paraan, tinutukoy namin ang generic BAGO ang pamamaraan dahil kung gagamitin namin ang generic pagkatapos ng pamamaraan, hindi malalaman ng Java kung aling uri ang gagamitin. Samakatuwid, una naming inanunsyo na gagamitin namin ang generic na T, at pagkatapos ay sasabihin namin na ibabalik namin ang generic na ito. Natural, Util.<Integer>getValue(element, String.class)ito ay mabibigo sa isang error incompatible types: Class<String> cannot be converted to Class<Integer>. Kapag gumagamit ng mga na-type na pamamaraan, dapat mong laging tandaan ang tungkol sa pagbubura ng uri. Tingnan natin ang isang halimbawa:
import java.util.*;
public class HelloWorld {

    public static class Util {
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList(2, 3);
		for (Object element : list) {
		    System.out.println(Util.<Integer>getValue(element) + 1);
		}
    }
}
Ito ay gagana nang mahusay. Ngunit hangga't naiintindihan ng compiler na ang tinatawag na pamamaraan ay may uri ng Integer. Palitan natin ang output ng console ng sumusunod na linya: System.out.println(Util.getValue(element) + 1); At nakuha natin ang error: masamang mga uri ng operand para sa binary operator '+', unang uri: Bagay , pangalawang uri: int Ibig sabihin, nabura ang mga uri. Nakikita ng compiler na walang tinukoy ang uri, ang uri ay tinukoy bilang Object at nabigo ang pagpapatupad ng code na may error.
Теория дженериков в Java or How на практике ставить скобки - 8

Mga Generic na Uri

Maaari mong i-type hindi lamang ang mga pamamaraan, kundi pati na rin ang mga klase sa kanilang sarili. Ang Oracle ay may seksyong " Mga Pangkalahatang Uri " na nakatuon dito sa kanilang gabay. Tingnan natin ang isang halimbawa:
public static class SomeType<T> {
	public <E> void test(Collection<E> collection) {
		for (E element : collection) {
			System.out.println(element);
		}
	}
	public void test(List<Integer> collection) {
		for (Integer element : collection) {
			System.out.println(element);
		}
	}
}
Simple lang ang lahat dito. Kung gagamit tayo ng isang klase, ang generic ay nakalista pagkatapos ng pangalan ng klase. Gumawa tayo ngayon ng isang instance ng klase na ito sa pangunahing pamamaraan:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Ito ay gagana nang maayos. Nakikita ng compiler na mayroong Listahan ng mga numero at Koleksyon ng uri ng String. Ngunit paano kung burahin natin ang mga generic at gawin ito:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Makukuha natin ang error: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer I-type muli ang erasure. Dahil wala nang generic ang klase, nagpasya ang compiler na dahil nagpasa kami ng List, mas angkop ang isang method na may List<Integer>. At nahulog tayo sa isang pagkakamali. Samakatuwid, panuntunan #2: Kung ang isang klase ay nai-type, palaging tukuyin ang uri sa generic .

Mga paghihigpit

Maaari kaming maglapat ng paghihigpit sa mga uri na tinukoy sa generics. Halimbawa, gusto naming tanggapin lamang ng container ang Numero bilang input. Ang tampok na ito ay inilarawan sa Oracle Tutorial sa Bounded Type Parameters na seksyon . Tingnan natin ang isang halimbawa:
import java.util.*;
public class HelloWorld{

    public static class NumberContainer<T extends Number> {
        private T number;

        public NumberContainer(T number)  { this.number = number; }

        public void print() {
            System.out.println(number);
        }
    }

    public static void main(String []args) {
		NumberContainer number1 = new NumberContainer(2L);
		NumberContainer number2 = new NumberContainer(1);
		NumberContainer number3 = new NumberContainer("f");
    }
}
Gaya ng nakikita mo, nilimitahan namin ang generic na uri upang maging klase/interface ng Numero at ang mga inapo nito. Nang kawili-wili, maaari mong tukuyin hindi lamang ang isang klase, kundi pati na rin ang mga interface. Halimbawa: public static class NumberContainer<T extends Number & Comparable> { Ang mga generic ay mayroon ding konsepto ng Wildcard https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Sila naman, ay nahahati sa tatlong uri: Nalalapat ang tinatawag na Get Put na prinsipyo sa Mga Wildcard . Maaari silang ipahayag sa sumusunod na anyo:
Теория дженериков в Java or How на практике ставить скобки - 9
Ang prinsipyong ito ay tinatawag ding prinsipyo ng PECS (Producer Extends Consumer Super). Maaari kang magbasa ng higit pa tungkol sa Habré sa artikulong " Paggamit ng mga generic na wildcard upang mapabuti ang kakayahang magamit ng Java API ", pati na rin sa mahusay na talakayan sa stackoverflow: " Paggamit ng mga wildcard sa Generics Java ". Narito ang isang maliit na halimbawa mula sa pinagmulan ng Java - ang paraan ng Collections.copy:
Теория дженериков в Java or How на практике ставить скобки - 10
Well, isang maliit na halimbawa kung paano ito HINDI gagana:
public static class TestClass {
	public static void print(List<? extends String> list) {
		list.add("Hello World!");
		System.out.println(list.get(0));
	}
}

public static void main(String []args) {
	List<String> list = new ArrayList<>();
	TestClass.print(list);
}
Pero kung papalitan mo ng super ang extend, magiging maayos ang lahat. Dahil pinupunan namin ang listahan ng isang halaga bago ito i-output, ito ay isang mamimili para sa amin, iyon ay, isang mamimili. Samakatuwid, gumagamit kami ng super.

Mana

May isa pang hindi pangkaraniwang katangian ng generics - ang kanilang pamana. Inheritance ng generics ay inilalarawan sa Oracle tutorial sa seksyong " Generics, Inheritance, at Subtypes ". Ang pangunahing bagay ay tandaan at mapagtanto ang mga sumusunod. Hindi natin ito magagawa:
List<CharSequence> list1 = new ArrayList<String>();
Dahil iba ang gumagana ng mana sa mga generic:
Теория дженериков в Java or How на практике ставить скобки - 11
At narito ang isa pang magandang halimbawa na mabibigo sa isang error:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Ang lahat ay simple din dito. Ang List<String> ay hindi isang inapo ng List<Object>, bagaman ang String ay isang inapo ng Object.

Pangwakas

Kaya na-refresh namin ang aming memorya ng generics. Kung ang mga ito ay bihirang ginagamit sa lahat ng kanilang kapangyarihan, ang ilang mga detalye ay mawawala sa memorya. Umaasa ako na ang maikling pagsusuri na ito ay makakatulong sa pag-refresh ng iyong memorya. At para sa mas malaking resulta, lubos kong inirerekumenda na basahin mo ang mga sumusunod na materyales: #Viacheslav
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION