CodeGym /Java Blog /Random /Java Generics: kung paano gamitin ang mga angled bracket ...
John Squirrels
Antas
San Francisco

Java Generics: kung paano gamitin ang mga angled bracket sa pagsasanay

Nai-publish sa grupo

Panimula

Simula sa JSE 5.0, idinagdag ang mga generic sa arsenal ng wikang Java.

Ano ang generics sa java?

Ang mga generic ay ang espesyal na mekanismo ng Java para sa pagpapatupad ng generic na programming — isang paraan upang ilarawan ang data at mga algorithm na hinahayaan kang magtrabaho sa iba't ibang uri ng data nang hindi binabago ang paglalarawan ng mga algorithm. Ang website ng Oracle ay may hiwalay na tutorial na nakatuon sa generics: " Lesson ". Upang maunawaan ang mga generic, kailangan mo munang malaman kung bakit kailangan ang mga ito at kung ano ang ibinibigay ng mga ito. Ang seksyong " Why Use Generics? " ng tutorial ay nagsasabi na ang ilang layunin ay mas malakas na pagsuri ng uri sa oras ng pag-compile at pag-aalis ng pangangailangan para sa mga tahasang cast. Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 1Maghanda tayo para sa ilang mga pagsubok sa aming minamahal na Tutorialspoint online java compiler. Ipagpalagay na mayroon kang sumusunod na code:

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 ang amo ay dumating sa amin at sabihin na "Hello, world!" ay isang labis na nagamit na parirala at na dapat mong ibalik lamang ang "Hello"? Aalisin namin ang code na nagsasama-sama ng ", mundo!" Ito ay tila hindi nakakapinsala, tama? Ngunit talagang nakakakuha kami ng isang error SA COMPILE TIME:

error: incompatible types: Object cannot be converted to String
Ang problema ay na sa aming Listahan ay nag-iimbak ng Mga Bagay. Ang String ay isang inapo ng Object (dahil ang lahat ng mga klase ng Java ay tahasang nagmamana ng Object ), na nangangahulugang kailangan namin ng isang tahasang cast, ngunit hindi kami nagdagdag ng isa. Sa panahon ng operasyon ng concatenation, ang static na String.valueOf(obj) na paraan ay tatawagin gamit ang object. Sa kalaunan, tatawagin nito ang paraan ng toString ng klase ng Bagay . Sa madaling salita, ang aming Listahan ay naglalaman ng isang Bagay . Nangangahulugan ito na saanman kailangan namin ng isang partikular na uri (hindi Object ), kakailanganin naming gawin mismo ang uri ng conversion:

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 List ay kumukuha ng mga bagay, maaari itong mag-imbak hindi lamang ng String s, kundi pati na rin ng Integer s. Ngunit ang pinakamasama ay ang compiler ay walang nakikitang mali dito. At ngayon ay makakakuha tayo ng error SA RUN TIME (kilala bilang "runtime error"). Ang error ay magiging:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Dapat kang sumang-ayon na hindi ito napakahusay. At ang lahat ng ito dahil ang compiler ay hindi isang artificial intelligence na may kakayahang laging hulaan nang tama ang layunin ng programmer. Ipinakilala ng Java SE 5 ang mga generic upang hayaan kaming sabihin sa compiler ang tungkol sa aming mga intensyon — tungkol sa kung aling mga uri ang aming gagamitin. Inaayos namin ang aming code sa pamamagitan ng pagsasabi sa compiler kung ano ang gusto namin:

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);
		}
	}
}
Gaya ng nakikita mo, hindi na namin kailangan ng cast sa isang String . Bilang karagdagan, mayroon kaming mga anggulong bracket na nakapalibot sa uri ng argumento. Ngayon ay hindi tayo papayagan ng compiler na i-compile ang klase hanggang sa alisin natin ang linyang nagdaragdag ng 123 sa listahan, dahil ito ay isang Integer . At sasabihin nito sa atin. Tinatawag ng maraming tao ang generics na "syntactic sugar". At tama sila, dahil pagkatapos ma-compile ang mga generic, talagang nagiging parehong uri sila ng mga conversion. Tingnan natin ang bytecode ng mga pinagsama-samang klase: isa na gumagamit ng tahasang cast at isa na gumagamit ng generics: Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 2Pagkatapos ng compilation, lahat ng generics ay mabubura. Ito ay tinatawag na " type erasure". Ang uri ng erasure at generics ay idinisenyo upang maging backward compatible sa mga mas lumang bersyon ng JDK habang sabay na pinapayagan ang compiler na tumulong sa mga uri ng kahulugan sa mga bagong bersyon ng Java.

Mga hilaw na uri

Sa pagsasalita tungkol sa generics, palagi kaming may dalawang kategorya: mga parameterized na uri at raw na uri. Ang mga raw na uri ay mga uri na nag-aalis ng "paglilinaw ng uri" sa mga anggulong bracket: Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 3Ang mga naka-parameter na uri, sa kamay, ay may kasamang "paglilinaw": Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 4Gaya ng nakikita mo, gumamit kami ng hindi pangkaraniwang konstruksyon, na minarkahan ng isang arrow sa screenshot. Ito ay espesyal na syntax na idinagdag sa Java SE 7. Ito ay tinatawag na " brilyante ". Bakit? Ang mga angle bracket ay bumubuo ng brilyante: <> . Dapat mo ring malaman na ang diamond syntax ay nauugnay sa konsepto ng " type inference ". Pagkatapos ng lahat, ang compiler, nakikita <>sa kanan, tumitingin sa kaliwang bahagi ng assignment operator, kung saan makikita ang uri ng variable na ang value ay itinalaga. Batay sa kung ano ang nahanap nito sa bahaging ito, nauunawaan nito ang uri ng halaga sa kanan. Sa katunayan, kung ang isang generic na uri ay ibinigay sa kaliwa, ngunit hindi sa kanan, 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);
	}
}
Ngunit pinaghahalo nito ang bagong istilo sa mga generic at ang lumang istilo nang wala ang mga ito. At ito ay lubos na hindi kanais-nais. Kapag kino-compile ang code sa itaas, nakukuha namin ang sumusunod na mensahe:

Note: HelloWorld.java uses unchecked or unsafe operations
Sa katunayan, ang dahilan kung bakit kailangan mo pang magdagdag ng brilyante dito ay tila hindi maintindihan. 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);
	}
}
Maaalala mo na ang ArrayList ay may pangalawang tagabuo na kumukuha ng koleksyon bilang argumento. At ito ay kung saan ang isang bagay na makasalanan ay nakatago. Kung wala ang diamond syntax, hindi naiintindihan ng compiler na niloloko ito. Gamit ang syntax ng brilyante, ginagawa nito. Kaya, ang Rule #1 ay: palaging gamitin ang diamond syntax na may mga parameterized na uri. Kung hindi, nanganganib kaming mawala kung saan kami gumagamit ng mga hilaw na uri. Upang alisin ang mga babala na "gumagamit ng hindi naka-check o hindi ligtas na mga operasyon," maaari naming gamitin ang @SuppressWarnings("unchecked") annotation sa isang paraan o klase. Ngunit isipin kung bakit mo napagpasyahan na gamitin ito. Tandaan ang unang panuntunan. Siguro kailangan mong magdagdag ng isang uri ng argumento.

Mga Generic na pamamaraan ng Java

Hinahayaan ka ng mga generic na lumikha ng mga pamamaraan na ang mga uri ng parameter at uri ng pagbabalik ay naka-parameter. Ang isang hiwalay na seksyon ay nakatuon sa kakayahang ito sa Oracle tutorial: " Mga Generic na Paraan ". Mahalagang tandaan ang syntax na itinuro sa tutorial na ito:
  • kabilang dito ang isang listahan ng mga parameter ng uri sa loob ng mga anggulong bracket;
  • ang listahan ng mga parameter ng uri ay nauuna sa uri ng pagbabalik ng pamamaraan.
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 mo na mayroon itong dalawang generic na pamamaraan. Salamat sa posibilidad ng uri ng inference, maaari naming ipahiwatig ang 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 gumagawa ng maraming kahulugan kung iisipin mo ito. Kapag nagdedeklara ng generic na pamamaraan, tinutukoy namin ang uri ng parameter BAGO ang pamamaraan, dahil kung idedeklara namin ang uri ng parameter pagkatapos ng pamamaraan, hindi malalaman ng JVM kung aling uri ang gagamitin. Alinsunod dito, ipinapahayag muna namin na gagamitin namin ang parameter ng uri ng T , at pagkatapos ay sasabihin namin na ibabalik namin ang ganitong uri. Natural, ang Util.<Integer>getValue(element, String.class) ay mabibigo nang may error:hindi tugmang mga uri: Class<String> ay hindi maaaring i-convert sa Class<Integer> . Kapag gumagamit ng mga generic na pamamaraan, dapat mong laging tandaan ang uri ng pagbura. 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 tatakbo nang maayos. Ngunit hangga't naiintindihan ng tagatala na ang uri ng pagbabalik ng pamamaraan na tinatawag ay Integer . Palitan ang console output statement ng sumusunod na linya:

System.out.println(Util.getValue(element) + 1);
Nakakakuha kami ng error:

bad operand types for binary operator '+', first type: Object, second type: int.
Sa madaling salita, naganap ang pagbubura ng uri. Nakikita ng compiler na walang tinukoy ang uri, kaya ang uri ay ipinahiwatig bilang Bagay at nabigo ang pamamaraan na may error.

Mga generic na klase

Hindi lamang mga pamamaraan ang maaaring ma-parameter. Pwede rin ang mga klase. Ang seksyong "Mga Pangkalahatang Uri" ng tutorial ng Oracle ay nakatuon dito. Isaalang-alang 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 gagamitin namin ang generic na klase, ang uri ng parameter ay ipinahiwatig pagkatapos ng pangalan ng klase. Ngayon, lumikha tayo ng isang halimbawa 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);
}
Ang code na ito ay gagana nang maayos. Nakikita ng compiler na mayroong Listahan ng mga numero at Koleksyon ng mga String . Ngunit paano kung alisin natin ang uri ng parameter at gawin ito:

SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Nakakakuha kami ng error:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Muli, ito ay uri ng pagbura. Dahil ang klase ay hindi na gumagamit ng isang uri ng parameter, ang compiler ay nagpasya na, dahil naipasa namin ang isang List , ang pamamaraan na may List<Integer> ay pinakaangkop. At nabigo tayo sa isang pagkakamali. Samakatuwid, mayroon kaming Rule #2: Kung mayroon kang generic na klase, palaging tukuyin ang mga parameter ng uri.

Mga paghihigpit

Maaari naming paghigpitan ang mga uri na tinukoy sa mga generic na pamamaraan at mga klase. Halimbawa, ipagpalagay na gusto naming tanggapin lamang ng isang lalagyan ang isang Numero bilang uri ng argumento. Ang tampok na ito ay inilarawan sa seksyon ng Mga Bounded Type Parameter ng tutorial ng Oracle. 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, pinaghigpitan namin ang uri ng parameter sa klase/interface ng Numero o sa mga inapo nito. Tandaan na maaari mong tukuyin hindi lamang ang isang klase, kundi pati na rin ang mga interface. Halimbawa:

public static class NumberContainer<T extends Number & Comparable> {
Sinusuportahan din ng mga generic ang mga wildcard Nahahati sila sa tatlong uri: Ang iyong paggamit ng mga wildcard ay dapat sumunod sa prinsipyo ng Get-Put . Maaari itong ipahayag tulad ng sumusunod:
  • Gumamit ng extend wildcard kapag nakakuha ka lang ng mga value mula sa isang structure.
  • Gumamit ng super wildcard kapag naglagay ka lang ng mga value sa isang structure.
  • At huwag gumamit ng wildcard kapag pareho kayong gustong kunin at ilagay mula/sa isang istraktura.
Ang prinsipyong ito ay tinatawag ding prinsipyo ng Producer Extends Consumer Super (PECS). Narito ang isang maliit na halimbawa mula sa source code para sa pamamaraan ng Collections.copy ng Java : Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 5At narito ang isang maliit na halimbawa ng kung ano ang 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);
}
Ngunit kung papalitan mo ang extend ng super , ayos lang ang lahat. Dahil pinupuno namin ang listahan ng isang halaga bago ipakita ang mga nilalaman nito, isa itong consumer . Alinsunod dito, gumagamit kami ng super.

Mana

Ang mga generic ay may isa pang kawili-wiling tampok: mana. Ang paraan ng paggana ng mana para sa mga generic ay inilarawan sa ilalim ng " Mga Generic, Pamana, at Mga Subtype " sa tutorial ng Oracle. Ang mahalagang bagay ay tandaan at kilalanin ang mga sumusunod. Hindi natin ito magagawa:

List<CharSequence> list1 = new ArrayList<String>();
Dahil iba ang gumagana ng mana sa mga generic: Generics sa Java: kung paano gamitin ang mga angled bracket sa pagsasanay - 6At narito ang isa pang magandang halimbawa na mabibigo nang may error:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Muli, ang lahat ay simple dito. Ang List<String> ay hindi isang inapo ng List<Object> , kahit na ang String ay isang inapo ng Object . Upang palakasin ang iyong natutunan, iminumungkahi naming manood ka ng isang video lesson mula sa aming Java Course

Konklusyon

Kaya na-refresh namin ang aming memorya tungkol sa generics. Kung bihira mong mapakinabangan nang husto ang kanilang mga kakayahan, nagiging malabo ang ilan sa mga detalye. Umaasa ako na ang maikling pagsusuri na ito ay nakatulong sa iyong memorya. Para sa mas mahusay na mga resulta, lubos kong inirerekumenda na pamilyar ka sa sumusunod na materyal:
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION