1. การเสนอแนวคิดของยาชื่อสามัญ (ทำไมต้องใช้ยาสามัญ)?
ก่อนอื่นมาดูรหัสสั้นต่อไปนี้:
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {list list = new ArrayList (); list.add ("qqyumidi"); list.add ("ข้าวโพด"); list.add (100); สำหรับ (int i = 0; i <list.size (); i ++) {string name = (string) list.get (i); // 1 system.out.println ("ชื่อ:" + ชื่อ); -กำหนดคอลเลกชันของประเภทรายการก่อนเพิ่มค่าประเภทสตริงสองค่าลงไปแล้วเพิ่มค่าประเภทจำนวนเต็ม สิ่งนี้ได้รับอนุญาตอย่างสมบูรณ์เนื่องจากประเภทของรายการเริ่มต้นเป็นวัตถุในเวลานี้ ในลูปที่ตามมามันเป็นเรื่องง่ายที่จะมีข้อผิดพลาดคล้ายกับ // 1 เพราะฉันลืมที่จะเพิ่มค่าประเภทจำนวนเต็มหรือเหตุผลการเข้ารหัสอื่น ๆ ในรายการก่อนหน้านี้ เนื่องจากขั้นตอนการรวบรวมเป็นเรื่องปกติข้อยกเว้น "java.lang.classcastexception" จะปรากฏขึ้นที่รันไทม์ ดังนั้นข้อผิดพลาดดังกล่าวจึงตรวจจับได้ยากในระหว่างกระบวนการเข้ารหัส
ในระหว่างกระบวนการเข้ารหัสดังกล่าวข้างต้นเราพบว่ามีสองปัญหาหลัก:
1. เมื่อเราใส่วัตถุลงในคอลเลกชันคอลเลกชันจะไม่จำประเภทของวัตถุนี้ เมื่อวัตถุนี้ถูกนำออกมาจากคอลเลกชันอีกครั้งประเภทการรวบรวมของวัตถุที่เปลี่ยนแปลงจะกลายเป็นประเภทวัตถุ แต่ประเภทรันไทม์ของมันยังคงเป็นประเภทของตัวเอง
2. ดังนั้นเมื่อนำองค์ประกอบคอลเลกชันออกมาที่ // 1 ประเภทที่ถูกบังคับเทียมจะต้องถูกแปลงเป็นประเภทเป้าหมายเฉพาะและเป็นเรื่องง่ายที่จะเห็นข้อยกเว้น "java.lang.classcastexception"
ดังนั้นมีวิธีใดบ้างที่จะทำให้คอลเลกชันจดจำองค์ประกอบประเภทต่าง ๆ ในคอลเลกชันและเพื่อให้บรรลุว่าตราบใดที่ไม่มีปัญหาในระหว่างการรวบรวมจะไม่มี "java.lang.classcastexception" ในระหว่างการรันไทม์? คำตอบคือการใช้ยาชื่อสามัญ
2. ทั่วไปคืออะไร?
ทั่วไปนั่นคือ "ประเภทพารามิเตอร์" เมื่อพูดถึงพารามิเตอร์สิ่งที่คุ้นเคยที่สุดคือการมีพารามิเตอร์ที่เป็นรูปธรรมเมื่อกำหนดวิธีการแล้วส่งพารามิเตอร์จริงเมื่อเรียกใช้วิธีนี้ ดังนั้นคุณจะเข้าใจประเภทการกำหนดพารามิเตอร์ได้อย่างไร? ตามชื่อแนะนำมันหมายถึงการกำหนดพารามิเตอร์ประเภทจากประเภทเฉพาะดั้งเดิมคล้ายกับพารามิเตอร์ตัวแปรในวิธีการ ในเวลานี้ประเภทจะถูกกำหนดเป็นรูปแบบพารามิเตอร์ (สามารถเรียกได้ว่าเป็นพารามิเตอร์ที่เป็นทางการประเภท) จากนั้นประเภทเฉพาะ (ประเภทพารามิเตอร์จริง) จะถูกส่งผ่านเมื่อใช้/เรียกใช้
ดูเหมือนจะซับซ้อนเล็กน้อย ก่อนอื่นมาดูตัวอย่างข้างต้นโดยใช้การเขียนทั่วไป
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) { /* รายการรายการ = new ArrayList (); list.add ("qqyumidi"); list.add ("ข้าวโพด"); list.add (100); */ list <String> list = new ArrayList <String> (); list.add ("qqyumidi"); list.add ("ข้าวโพด"); //list.add(100); // 1 แจ้งข้อผิดพลาดในการรวบรวม (int i = 0; i <list.size (); i ++) {string name = list.get (i); // 2 System.out.println ("ชื่อ:" + ชื่อ); -หลังจากใช้การเขียนทั่วไปข้อผิดพลาดในการรวบรวมจะเกิดขึ้นเมื่อคุณต้องการเพิ่มวัตถุประเภทจำนวนเต็มที่ // 1 ผ่านรายการ <String> มีข้อ จำกัด โดยตรงว่าองค์ประกอบของประเภทสตริงเท่านั้นที่สามารถอยู่ในรายการรวบรวมรายการดังนั้นจึงไม่จำเป็นต้องใช้ประเภทที่ // 2 เนื่องจากในเวลานี้คอลเลกชันสามารถจดจำข้อมูลประเภทขององค์ประกอบและคอมไพเลอร์สามารถยืนยันได้ว่าเป็นประเภทสตริง
เมื่อรวมคำจำกัดความทั่วไปข้างต้นเรารู้ว่าในรายการ <String>, สตริงเป็นพารามิเตอร์ประเภทนั่นคืออินเตอร์เฟสรายการที่สอดคล้องกันจะต้องมีพารามิเตอร์ประเภทที่เป็นทางการ ยิ่งไปกว่านั้นผลการส่งคืนของวิธีการ Get () เป็นประเภทพารามิเตอร์ที่เป็นทางการโดยตรงนี้ (นั่นคือพารามิเตอร์ประเภทขาเข้าที่สอดคล้องกัน) มาดูคำจำกัดความเฉพาะของอินเทอร์เฟซรายการ:
รายการอินเตอร์เฟสสาธารณะ <e> ขยายคอลเลกชัน <e> {ขนาด int (); บูลีน isempty (); บูลีนประกอบด้วย (Object O); ตัววนซ้ำ <e> iterator (); วัตถุ [] toarray (); <t> t [] toarray (t [] a); บูลีนเพิ่ม (e e); บูลีนลบ (วัตถุ o); บูลีนประกอบด้วย (คอลเลกชัน <?> c); บูลีน Addall (คอลเลกชัน <? ขยาย e> c); บูลีน Addall (ดัชนี int, คอลเลกชัน <? ขยาย e> c); บูลีน RemoveAll (คอลเลกชัน <?> c); บูลีน retainall (คอลเลกชัน <?> c); เป็นโมฆะชัดเจน (); บูลีนเท่ากับ (วัตถุ o); int hashcode (); E รับ (INT ดัชนี); E SET (INT INDEX, E Element); เป็นโมฆะเพิ่ม (ดัชนี int, องค์ประกอบ e); E ลบ (INT ดัชนี); INT INDEXOF (Object O); int lastindexof (Object O); listiterator <e> listiterator (); listiterator <e> listiterator (ดัชนี int); รายการ <e> sublist (int fromindex, int toindex);}เราจะเห็นได้ว่าหลังจากคำจำกัดความทั่วไปถูกนำมาใช้ในอินเทอร์เฟซรายการ E ใน <E> หมายถึงพารามิเตอร์ประเภทที่เป็นทางการซึ่งสามารถรับพารามิเตอร์ประเภทเฉพาะได้ ในคำจำกัดความของอินเทอร์เฟซนี้ที่ E ปรากฏขึ้นหมายความว่าพารามิเตอร์ประเภทเดียวกันที่ยอมรับจากภายนอกได้รับการยอมรับ
โดยธรรมชาติแล้ว ArrayList เป็นคลาสการใช้งานสำหรับอินเทอร์เฟซรายการและแบบฟอร์มคำจำกัดความคือ:
จากนี้เราเข้าใจจากมุมมองของซอร์สโค้ดว่าทำไมวัตถุประเภทจำนวนเต็มถูกรวบรวมอย่างไม่ถูกต้องที่ // 1 และประเภทที่ได้รับ AT // 2 เป็นประเภทสตริงโดยตรง
ArrayList คลาสสาธารณะ <E> ขยาย AbstractList <E> ใช้รายการ <E>, RandomAccess, cloneable, java.io.serializable {บูลีนสาธารณะเพิ่ม (e e) {ensureCapacityInternal (ขนาด + 1); // เพิ่ม modcount !! ElementData [ขนาด ++] = E; กลับมาจริง; } สาธารณะ e get (int index) {rangecheck (ดัชนี); checkforcomodification (); return arraylist.his.elementData (ออฟเซ็ต + ดัชนี); } //...mit กระบวนการนิยามเฉพาะอื่น ๆ }3. ปรับแต่งอินเทอร์เฟซทั่วไปคลาสทั่วไปและวิธีการทั่วไป
จากเนื้อหาข้างต้นทุกคนเข้าใจกระบวนการดำเนินการเฉพาะของยาสามัญ เป็นที่ทราบกันดีว่าอินเทอร์เฟซคลาสและวิธีการสามารถกำหนดได้โดยใช้ยาชื่อสามัญและใช้ตามลำดับ ใช่เมื่อใช้โดยเฉพาะมันสามารถแบ่งออกเป็นอินเทอร์เฟซทั่วไปคลาสทั่วไปและวิธีการทั่วไป
อินเทอร์เฟซทั่วไปที่กำหนดเองคลาสทั่วไปและวิธีการทั่วไปคล้ายกับรายการและ arraylist ในซอร์สโค้ด Java ด้านบน ดังต่อไปนี้เราดูที่คลาสทั่วไปและคำจำกัดความวิธีการที่ง่ายที่สุด:
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <string> name = new box <String> ("ข้าวโพด"); System.out.println ("ชื่อ:" + name.getData ()); }} กล่องคลาส <t> {ข้อมูลส่วนตัว t; กล่องสาธารณะ () {} กล่องสาธารณะ (ข้อมูล t) {this.data = data; } สาธารณะ t getData () {ส่งคืนข้อมูล; -ในกระบวนการกำหนดอินเทอร์เฟซทั่วไปคลาสทั่วไปและวิธีการทั่วไปพารามิเตอร์ทั่วไปของเราเช่น T, E, K, V ฯลฯ มักใช้เพื่อแสดงถึงพารามิเตอร์ที่เป็นทางการทั่วไปเนื่องจากพวกเขาได้รับพารามิเตอร์ประเภทที่ส่งผ่านจากการใช้งานภายนอก ดังนั้นสำหรับพารามิเตอร์ประเภทที่แตกต่างกันประเภทของอินสแตนซ์วัตถุที่เกี่ยวข้องที่สร้างขึ้นเหมือนกันหรือไม่?
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <string> name = new box <String> ("ข้าวโพด"); Box <Integer> age = กล่องใหม่ <จำนวนเต็ม> (712); System.out.println ("คลาสชื่อ:" + name.getClass ()); // com.qqyumidi.box system.out.println ("คลาสอายุ:" + age.getClass ()); // com.qqyumidi.box system.out.println (name.getClass () == age.getClass ()); // จริง }}จากนี้เราพบว่าเมื่อใช้คลาสทั่วไปแม้ว่าจะมีการโต้แย้งทั่วไปที่แตกต่างกัน แต่ประเภทต่าง ๆ จะไม่ถูกสร้างขึ้นในความหมายที่แท้จริง มีคลาสทั่วไปเพียงคลาสเดียวที่ผ่านในอาร์กิวเมนต์ทั่วไปที่แตกต่างกันในหน่วยความจำนั่นคือมันยังคงเป็นประเภทพื้นฐานที่สุด (กล่องในตัวอย่างนี้) แน่นอนว่ามีเหตุผลเราสามารถเข้าใจได้ว่าเป็นประเภททั่วไปที่แตกต่างกันหลายประเภท
เหตุผลก็คือจุดประสงค์ของแนวคิดของยาสามัญใน Java คือมันใช้งานได้ในขั้นตอนการรวบรวมรหัสเท่านั้น ในระหว่างกระบวนการรวบรวมหลังจากตรวจสอบผลลัพธ์ทั่วไปอย่างถูกต้องข้อมูลที่เกี่ยวข้องของยาชื่อสามัญจะถูกลบ กล่าวคือไฟล์คลาสที่รวบรวมได้สำเร็จไม่มีข้อมูลทั่วไปใด ๆ ข้อมูลทั่วไปจะไม่เข้าสู่ขั้นตอนรันไทม์
สิ่งนี้สรุปได้ในประโยคเดียว: ประเภททั่วไปถือว่าเป็นแบบมีเหตุผลหลายประเภทและเป็นประเภทพื้นฐานเดียวกัน
สี่. พิมพ์ไวด์การ์ด
จากข้อสรุปข้างต้นเรารู้ว่ากล่อง <number> และกล่อง <จำนวนเต็ม> เป็นจริงทั้งสองประเภทกล่อง ตอนนี้เราต้องสำรวจคำถามต่อไป ดังนั้นอย่างมีเหตุผลสามารถกล่อง <number> และกล่อง <จำนวนเต็ม> ได้รับการยกย่องว่าเป็นประเภททั่วไปที่มีความสัมพันธ์ระหว่างพ่อแม่กับลูก?
เพื่อชี้แจงปัญหานี้ลองดูตัวอย่างต่อไปนี้ต่อไป:
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <gumber> name = box ใหม่ <gumber> (99); Box <Integer> age = กล่องใหม่ <จำนวนเต็ม> (712); getData (ชื่อ); // วิธีการ getData (กล่อง <Gumber>) ในประเภท generictest คือ // ไม่สามารถใช้ได้กับอาร์กิวเมนต์ (กล่อง <teger>) getData (อายุ); // 1} โมฆะคงที่สาธารณะ getData (กล่อง <Gumber> ข้อมูล) {system.out.println ("ข้อมูล:" + data.getData ()); -เราพบว่าข้อความแสดงข้อผิดพลาดปรากฏขึ้นที่รหัส // 1: เมธอด getData (กล่อง <Gumber>) ใน t ype generictest ไม่สามารถใช้งานได้สำหรับอาร์กิวเมนต์ (กล่อง <จำนวนเต็ม>) เห็นได้ชัดว่าโดยการแจ้งข้อมูลเรารู้ว่ากล่อง <Gumber> ไม่สามารถพิจารณาอย่างมีเหตุผลว่าเป็นคลาสหลักของกล่อง <จำนวนเต็ม> แล้วเหตุผลคืออะไร?
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <teeger> a = กล่องใหม่ <teger> (712); กล่อง <Gumber> B = A; // 1 กล่อง <loot> f = กล่องใหม่ <loat> (3.14f); B.SetData (F); // 2} โมฆะคงที่สาธารณะ getData (กล่อง <Gumber> ข้อมูล) {system.out.println ("ข้อมูล:" + data.getData ()); }} กล่องคลาส <t> {ข้อมูลส่วนตัว t; กล่องสาธารณะ () {} กล่องสาธารณะ (ข้อมูล t) {setData (ข้อมูล); } สาธารณะ t getData () {ส่งคืนข้อมูล; } โมฆะสาธารณะ setData (ข้อมูล t) {this.data = ข้อมูล; -ในตัวอย่างนี้จะมีข้อความแสดงข้อผิดพลาดที่ // 1 และ // 2 ที่นี่เราสามารถใช้วิธีการตอบโต้เพื่ออธิบาย
สมมติว่ากล่อง <Gumber> สามารถถือได้อย่างมีเหตุผลว่าเป็นคลาสหลักของกล่อง <จำนวนเต็ม> จากนั้นจะไม่มีข้อผิดพลาดที่พร้อมที่ // 1 และ // 2 จากนั้นปัญหาก็เกิดขึ้น ประเภทใดเมื่อดึงข้อมูลผ่านวิธี getData () จำนวนเต็ม? ลอย? หรือหมายเลข? ยิ่งไปกว่านั้นเนื่องจากคำสั่งที่ไม่สามารถควบคุมได้ในกระบวนการเขียนโปรแกรมจึงต้องทำการตัดสินพิมพ์เมื่อจำเป็นและทำการแปลงประเภท เห็นได้ชัดว่าสิ่งนี้ขัดแย้งกับแนวคิดของยาชื่อสามัญดังนั้น Box <หมายเลข> ไม่สามารถถือได้ว่าเป็นคลาสหลักของกล่อง <จำนวนเต็ม>
โอเคลองย้อนกลับไปดูตัวอย่างแรกใน "ประเภทไวด์การ์ด" เรารู้เหตุผลที่ลึกซึ้งยิ่งขึ้นสำหรับการแจ้งข้อผิดพลาดเฉพาะของมัน แล้วจะแก้ปัญหาได้อย่างไร? สำนักงานใหญ่สามารถกำหนดฟังก์ชั่นใหม่ เห็นได้ชัดว่าสิ่งนี้ตรงกันข้ามกับแนวคิด polymorphism ใน Java ดังนั้นเราจึงต้องมีประเภทอ้างอิงที่สามารถใช้อย่างมีเหตุผลเพื่อแสดงคลาสแม่ของทั้งสองกล่อง <จำนวนเต็ม> และกล่อง <Gumber> และด้วยเหตุนี้ไวลด์การ์ดประเภทนี้จึงเข้ามา
ไวด์การ์ดพิมพ์โดยทั่วไปจะใช้แทนอาร์กิวเมนต์ประเภทเฉพาะ โปรดทราบว่านี่เป็นพารามิเตอร์ประเภทไม่ใช่พารามิเตอร์ประเภท! และกล่อง <?> เป็นแบบมีเหตุผลคลาสแม่ของกล่องทั้งหมด <integer>, กล่อง <Gumber> ... , ฯลฯ ดังนั้นเรายังสามารถกำหนดวิธีการทั่วไปเพื่อตอบสนองความต้องการดังกล่าว
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <string> name = new box <String> ("ข้าวโพด"); Box <Integer> age = กล่องใหม่ <จำนวนเต็ม> (712); กล่อง <Gumber> number = กล่องใหม่ <gumber> (314); getData (ชื่อ); getData (อายุ); getData (หมายเลข); } โมฆะคงที่สาธารณะ getData (กล่อง <?> data) {system.out.println ("data:" + data.getData ()); -บางครั้งเราอาจได้ยินเกี่ยวกับไวด์การ์ดประเภทบนและล่าง มันเป็นอย่างไร?
ในตัวอย่างข้างต้นหากคุณต้องการกำหนดวิธีที่ทำหน้าที่คล้ายกับ getData () แต่มีข้อ จำกัด เพิ่มเติมเกี่ยวกับอาร์กิวเมนต์ประเภท: มันสามารถเป็นคลาสตัวเลขและคลาสย่อยเท่านั้น ในเวลานี้จำเป็นต้องมีขีด จำกัด สูงสุดของไวด์การ์ดประเภท
คลาสสาธารณะ generictest {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {box <string> name = new box <String> ("ข้าวโพด"); Box <Integer> age = กล่องใหม่ <จำนวนเต็ม> (712); กล่อง <Gumber> number = กล่องใหม่ <gumber> (314); getData (ชื่อ); getData (อายุ); getData (หมายเลข); // getuppernumberdata (ชื่อ); // 1 getuppernumberdata (อายุ); // 2 getuppernumberdata (หมายเลข); // 3} โมฆะคงที่สาธารณะ getData (กล่อง <?> data) {system.out.println ("ข้อมูล:" + data.getData ()); } โมฆะคงที่สาธารณะ getUpperNumberData (กล่อง <? ขยายหมายเลข> ข้อมูล) {System.out.println ("ข้อมูล:" + data.getData ()); -ณ จุดนี้เห็นได้ชัดว่าการโทรที่รหัส // 1 จะปรากฏข้อความแสดงข้อผิดพลาดในขณะที่การโทรที่ // 2 // 3 จะเป็นปกติ
ขีด จำกัด สูงสุดของประเภทไวด์การ์ดถูกกำหนดโดยรูปแบบของกล่อง <? ขยายหมายเลข> ตามลำดับขีด จำกัด ล่างของไวด์การ์ดประเภทคือรูปแบบของกล่อง <? Super Number> และความหมายของมันตรงข้ามกับขีด จำกัด สูงสุดของประเภทไวด์การ์ด ฉันจะไม่อธิบายมากเกินไปที่นี่
5. บทพิเศษ
ตัวอย่างในบทความนี้ส่วนใหญ่อ้างถึงเพื่อแสดงให้เห็นถึงความคิดบางอย่างในเรื่องทั่วไปและไม่จำเป็นต้องใช้งานจริง นอกจากนี้เมื่อพูดถึงเรื่องทั่วไปฉันเชื่อว่าสิ่งที่คุณใช้ส่วนใหญ่อยู่ในคอลเลกชัน ในความเป็นจริงในกระบวนการเขียนโปรแกรมจริงคุณสามารถใช้ยาสามัญเพื่อทำให้การพัฒนาง่ายขึ้นและสามารถมั่นใจได้ว่าคุณภาพของรหัสได้ดี และสิ่งหนึ่งที่ควรทราบคือไม่มีอาร์เรย์ทั่วไปที่เรียกว่าในชวา
สำหรับทั่วไปสิ่งที่สำคัญที่สุดคือการเข้าใจความคิดและวัตถุประสงค์ที่อยู่เบื้องหลังพวกเขา
ข้างต้นคือการรวบรวมความรู้และข้อมูลเกี่ยวกับ Java Generics เราจะยังคงเพิ่มข้อมูลที่เกี่ยวข้องในอนาคต ขอบคุณสำหรับการสนับสนุนเว็บไซต์นี้!