จุดประสงค์ของบทความนี้คือการแนะนำ Java Generics เพื่อให้ทุกคนสามารถมีความเข้าใจขั้นสุดท้ายชัดเจนและแม่นยำในทุกด้านของ Java Generics และยังวางรากฐานสำหรับบทความต่อไป
การแนะนำ
ยาสามัญเป็นจุดความรู้ที่สำคัญมากในชวา ทั่วไปมีการใช้กันอย่างแพร่หลายในกรอบคอลเลกชัน Java ในบทความนี้เราจะดูการออกแบบของ Java Generics ตั้งแต่เริ่มต้นซึ่งจะเกี่ยวข้องกับการประมวลผล Wildcard และการลบประเภทที่น่าวิตก
พื้นฐานทั่วไป
ชั้นเรียนทั่วไป
ก่อนอื่นกำหนดคลาสกล่องง่าย ๆ :
กล่องคลาสสาธารณะ {วัตถุสตริงส่วนตัว; ชุดโมฆะสาธารณะ (วัตถุสตริง) {this.Object = Object; } สตริงสาธารณะ get () {return object; -นี่เป็นวิธีปฏิบัติที่พบบ่อยที่สุด หนึ่งในข้อเสียของสิ่งนี้คือองค์ประกอบประเภทสตริงเท่านั้นที่สามารถโหลดได้ในกล่อง ในอนาคตหากเราต้องการโหลดองค์ประกอบประเภทอื่นเช่นจำนวนเต็มเราจะต้องเขียนกล่องอื่นอีกครั้ง รหัสไม่สามารถนำกลับมาใช้ใหม่ได้และการใช้ยาสามัญสามารถแก้ปัญหานี้ได้ดี
กล่องชั้นเรียนสาธารณะ <T> {// t หมายถึง "ประเภท" ส่วนตัว t t; ชุดโมฆะสาธารณะ (t t) {this.t = t; } สาธารณะ t get () {return t; -วิธีนี้คลาสกล่องของเราสามารถนำกลับมาใช้ใหม่และเราสามารถแทนที่ t ด้วยประเภทใดก็ได้ที่เราต้องการ:
Box <Integer> IntegerBox = กล่องใหม่ <integer> (); Box <boal> doublebox = กล่องใหม่ <bould> (); Box <String> Stringbox = New Box <String> ();
วิธีการทั่วไป
หลังจากอ่านคลาสทั่วไปมาเรียนรู้เกี่ยวกับวิธีการทั่วไป การประกาศวิธีการทั่วไปนั้นง่ายเพียงเพิ่มรูปแบบที่คล้ายกับ <k, v> ไปยังประเภทการส่งคืน:
ชั้นเรียนสาธารณะ {สาธารณะคงที่ <k, v> boolean เปรียบเทียบ (pair <k, v> p1, pair <k, v> p2) {return p1.getKey (). เท่ากับ (p2.getKey ()) && p1.getValue () เท่ากับ (p2.getValue ()); }} คู่คลาสสาธารณะ <k, v> {คีย์ส่วนตัว k; มูลค่า V ส่วนตัว; คู่สาธารณะ (คีย์ k, ค่า V) {this.key = key; this.value = ค่า; } โมฆะสาธารณะ setKey (k key) {this.key = key; } โมฆะสาธารณะ setValue (ค่า V) {this.value = value; } public k getKey () {คีย์ return; } สาธารณะ v getValue () {ค่าคืน; -เราสามารถเรียกวิธีการทั่วไปเช่นนี้:
จับคู่ <จำนวนเต็ม, สตริง> p1 = คู่ใหม่ <> (1, "apple"); pair <จำนวนเต็ม, สตริง> p2 = คู่ใหม่ <> (2, "ลูกแพร์"); บูลีนเดียวกัน = util. <จำนวนเต็ม, สตริง> เปรียบเทียบ (p1, p2);
หรือใช้การอนุมานประเภทใน Java 1.7/1.8 เพื่อให้ Java อนุมานพารามิเตอร์ประเภทที่สอดคล้องกันโดยอัตโนมัติ:
จับคู่ <จำนวนเต็ม, สตริง> p1 = คู่ใหม่ <> (1, "apple"); pair <จำนวนเต็ม, สตริง> p2 = คู่ใหม่ <> (2, "ลูกแพร์"); บูลีนเดียวกัน = util.Compare (p1, p2);
สัญลักษณ์ขอบเขต
ตอนนี้เราต้องการใช้ฟังก์ชั่นดังกล่าวเพื่อค้นหาจำนวนองค์ประกอบในอาร์เรย์ทั่วไปที่มีขนาดใหญ่กว่าองค์ประกอบเฉพาะ เราสามารถนำไปใช้เช่นนี้:
สาธารณะคงที่ <t> int countgreaterthan (t [] anarray, t elem) {int count = 0; สำหรับ (t e: anarray) ถ้า (e> elem) // ข้อผิดพลาดคอมไพเลอร์ ++ นับ; นับคืน;}แต่เห็นได้ชัดว่าผิดเพราะยกเว้นประเภทดั้งเดิมเช่นสั้น, int, สอง, ยาว, ลอย, ไบต์, ถ่าน, ฯลฯ คลาสอื่น ๆ อาจไม่จำเป็นต้องใช้ตัวดำเนินการ> ดังนั้นคอมไพเลอร์รายงานข้อผิดพลาด วิธีแก้ปัญหานี้? คำตอบคือการใช้สัญลักษณ์ขอบเขต
อินเทอร์เฟซสาธารณะเทียบเท่า <t> {public int compereto (t o);}ทำการประกาศคล้ายกับต่อไปนี้ซึ่งเทียบเท่ากับการบอกคอมไพเลอร์ว่าพารามิเตอร์ประเภท t แสดงถึงคลาสที่ใช้อินเทอร์เฟซที่เปรียบเทียบได้ซึ่งเทียบเท่ากับการบอกคอมไพเลอร์ว่าพวกเขาทั้งหมดใช้วิธีการเปรียบเทียบอย่างน้อย
สาธารณะคงที่ <t ขยายการเปรียบเทียบ <t>> int countgreaterthan (t [] anarray, t elem) {int count = 0; สำหรับ (t e: anarray) ถ้า (e.compareto (elem)> 0) ++ นับ; นับคืน;}ไพ่
ก่อนที่จะทำความเข้าใจกับไวด์การ์ดก่อนอื่นเราต้องชี้แจงแนวคิดหรือยืมคลาสกล่องที่เรากำหนดไว้ข้างต้นสมมติว่าเราเพิ่มวิธีการเช่นนี้:
โมฆะสาธารณะ BOXTEST (กล่อง <Gumber> N) { / * ... * /}ดังนั้นพารามิเตอร์ประเภทใดที่ใช้ในการยอมรับพารามิเตอร์ เราสามารถส่งผ่านกล่อง <จำนวนเต็ม> หรือกล่อง <bould> ได้หรือไม่? คำตอบคือไม่ แม้ว่าจำนวนเต็มและสองเท่าเป็น subclasses ของจำนวน แต่ก็ไม่มีความสัมพันธ์ระหว่างกล่อง <จำนวนเต็ม> หรือกล่อง <boal> และกล่อง <Gumber> ในทั่วไป นี่เป็นสิ่งสำคัญมากและเราจะใช้ตัวอย่างที่สมบูรณ์เพื่อเพิ่มความเข้าใจของเราให้ลึกซึ้งยิ่งขึ้น
ก่อนอื่นเรากำหนดคลาสง่าย ๆ สองสามชั้นและเราจะใช้ด้านล่าง:
คลาสผลไม้ {} คลาสแอปเปิ้ลขยายผลไม้ {} คลาสสีส้มขยายผลไม้ {}ในตัวอย่างต่อไปนี้เราสร้างเครื่องอ่านคลาสทั่วไปจากนั้นใน F1 () เมื่อเราลองผล f = fruitreader.readexact (แอปเปิ้ล); คอมไพเลอร์จะรายงานข้อผิดพลาดเนื่องจากไม่มีความสัมพันธ์ระหว่างรายการ <fruit> และรายการ <apple>
คลาสสาธารณะทั่วไป {รายการคงที่ <apple> แอปเปิ้ล = array.aslist (ใหม่ Apple ()); รายการคงที่ <fluit> ผลไม้ = arrays.aslist (ผลไม้ใหม่ ()); เครื่องอ่านคลาสคงที่ <t> {t readExact (รายการ <t> รายการ) {return list.get (0); }} โมฆะคงที่ f1 () {reader <fruit> fruitreader = ใหม่ reader <fruit> (); // ข้อผิดพลาด: รายการ <fruit> ไม่สามารถนำไปใช้กับรายการ <apple> // ผล f = fruitreader.readexact (แอปเปิ้ล); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {f1 (); -แต่ตามพฤติกรรมการคิดตามปกติของเราจะต้องมีการเชื่อมต่อระหว่างแอปเปิ้ลและผลไม้ แต่คอมไพเลอร์ไม่สามารถจดจำได้ ดังนั้นฉันจะแก้ปัญหานี้ในรหัสทั่วไปได้อย่างไร เราสามารถแก้ปัญหานี้ได้โดยใช้ WildCards:
covarianTreader คลาสคงที่ <t> {t readcovariant (รายการ <? ขยายรายการ t>) {return list.get (0); }} โมฆะคงที่ f2 () {covarianTreader <fruit> fruiReader = new CovarianTreader <fruit> (); Fruit F = FruitReader.readCovariant (ผลไม้); Fruit A = FruitReader.ReadCovariant (แอปเปิ้ล);} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {f2 ();}สิ่งนี้ค่อนข้างคล้ายกับการบอกคอมไพเลอร์ว่าพารามิเตอร์ที่ได้รับการยอมรับจากวิธี readcovariant ของ FruitReader นั้นมีความยาวเท่ากับคลาสย่อยที่ตอบสนองผลไม้ (รวมถึงผลไม้เอง) เพื่อให้ความสัมพันธ์ระหว่างคลาสย่อยและคลาสแม่มีความสัมพันธ์กัน
หลักการ PECS
เราเห็นการใช้งานคล้ายกับ <? ขยาย t> ข้างต้น การใช้มันเราสามารถรับองค์ประกอบจากรายการดังนั้นเราสามารถเพิ่มองค์ประกอบลงในรายการได้หรือไม่? ลองกันเถอะ:
คลาสสาธารณะ Genericsandcovariance {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// wildcards อนุญาตให้เกิดความแปรปรวนร่วม: รายการ <? ขยายผลไม้> flist = arraylist ใหม่ <apple> (); // ข้อผิดพลาดในการรวบรวม: ไม่สามารถเพิ่มวัตถุประเภทใดก็ได้: // flist.add (Apple ใหม่ ()) // flist.add (ใหม่สีส้ม ()) // flist.add (ผลไม้ใหม่ ()) // flist.add (วัตถุใหม่ ()) flist.add (null); // ถูกกฎหมาย แต่ไม่น่าสนใจ // เรารู้ว่ามันกลับมาอย่างน้อยผลไม้: ผล f = flist.get (0); -คำตอบคือไม่คอมไพเลอร์ Java ไม่อนุญาตให้เราทำสิ่งนี้ทำไม? เราอาจพิจารณาปัญหานี้จากมุมมองของคอมไพเลอร์ เพราะรายการ <? ขยายผลไม้> FLIST สามารถมีความหมายได้มากมาย:
รายการ <? ขยายผลไม้> flist = arraylist ใหม่ <fruit> (); รายการ <? ขยายผลไม้> flist = new ArrayList <Apple> (); รายการ <? ขยายผลไม้> flist = new ArrayList <SURANGE> ();
ดังนั้นสำหรับคลาสการรวบรวมที่ใช้งาน <? ขยาย t> พวกเขาสามารถถือได้ว่าเป็นผู้ผลิตที่ให้ (รับ) องค์ประกอบภายนอกและไม่สามารถใช้เป็นผู้บริโภคเพื่อรับ (เพิ่ม) องค์ประกอบด้านนอก
เราควรทำอย่างไรถ้าเราต้องการเพิ่มองค์ประกอบ? คุณสามารถใช้ <? super t>:
คลาสสาธารณะทั่วไป {รายการคงที่ <apple> แอปเปิ้ล = new ArrayList <Apple> (); รายการคงที่ <fluit> ผลไม้ = arraylist ใหม่ <fruit> (); คงที่ <t> void writeExact (รายการ <t> รายการ, รายการ t) {list.add (รายการ); } โมฆะคงที่ f1 () {writeExact (แอปเปิ้ล, Apple ใหม่ ()); writeExact (ผลไม้แอปเปิ้ลใหม่ ()); } static <t> void writeWithWildCard (รายการ <? super t> รายการ, รายการ t) {list.add (รายการ)} โมฆะคงที่ f2 () {writeWithWildCard (แอปเปิ้ล, Apple ใหม่ ()); WriteWithWildCard (ผลไม้, Apple ใหม่ ()); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {f1 (); f2 (); -ด้วยวิธีนี้เราสามารถเพิ่มองค์ประกอบลงในคอนเทนเนอร์ แต่ข้อเสียของการใช้ Super คือเราไม่สามารถรับองค์ประกอบในคอนเทนเนอร์ในอนาคตได้ เหตุผลนั้นง่ายมาก เรายังคงพิจารณาปัญหานี้จากมุมมองของคอมไพเลอร์ สำหรับรายการ <? Super Apple> รายการมีความหมายต่อไปนี้:
รายการ <? Super Apple> list = new ArrayList <Apple> (); รายการ <? super apple> list = new ArrayList <Fruit> (); รายการ <? Super Apple> list = new ArrayList <Ojrop> ();
เมื่อเราพยายามที่จะได้รับแอปเปิ้ลผ่านรายการเราอาจได้รับผลไม้ซึ่งอาจเป็นผลไม้ประเภทอื่นเช่นส้ม
จากตัวอย่างข้างต้นเราสามารถสรุปกฎ "ผู้ผลิตขยาย, ผู้บริโภค Super":
หลังจากอ่านซอร์สโค้ดคอลเลกชัน Java บางตัวเราสามารถพบว่าเรามักจะใช้ทั้งสองด้วยกันเช่นต่อไปนี้:
คอลเลกชันระดับสาธารณะ {สาธารณะคงที่ <t> โมฆะคัดลอก (รายการ <? super t> dest, รายการ <? ขยาย t> src) {สำหรับ (int i = 0; i <src.size (); i ++) dest.set (i, src.get (i)); -ประเภทลบ
บางทีสิ่งที่น่าวิตกมากที่สุดเกี่ยวกับ Java Generics คือการลบประเภทโดยเฉพาะอย่างยิ่งสำหรับโปรแกรมเมอร์ที่มีประสบการณ์ C ++ การลบประเภทหมายความว่า Java Generics สามารถใช้สำหรับการตรวจสอบประเภทคงที่ระหว่างการรวบรวมและจากนั้นรหัสที่สร้างโดยคอมไพเลอร์จะลบข้อมูลประเภทที่สอดคล้องกัน ด้วยวิธีนี้ในระหว่างการวิ่ง JVM รู้ประเภทเฉพาะที่แสดงโดยทั่วไป จุดประสงค์ของเรื่องนี้เป็นเพราะสารยาสามัญ Java ได้รับการแนะนำหลังจาก 1.5 เพื่อรักษาความเข้ากันได้ลดลงคุณสามารถพิมพ์การลบให้เข้ากันได้กับรหัสที่ไม่ใช่ Generic ก่อนหน้า สำหรับจุดนี้หากคุณอ่านซอร์สโค้ดของกรอบคอลเลกชัน Java คุณจะพบว่าบางชั้นเรียนไม่สนับสนุนยาชื่อสามัญ
ต้องพูดมากการลบทั่วไปหมายถึงอะไร? ก่อนอื่นดูตัวอย่างง่ายๆต่อไปนี้:
โหนดระดับสาธารณะ <T> {ข้อมูลส่วนตัว;; โหนดส่วนตัว <T> ถัดไป; โหนดสาธารณะ (ข้อมูล t, โหนด <t> ถัดไป)} this.data = ข้อมูล; this.next = ถัดไป; } สาธารณะ t getData () {ส่งคืนข้อมูล; -หลังจากคอมไพเลอร์เสร็จสิ้นการตรวจสอบประเภทที่สอดคล้องกันรหัสด้านบนจะถูกแปลงเป็น:
โหนดระดับสาธารณะ {ข้อมูลวัตถุส่วนตัว; โหนดส่วนตัวถัดไป; โหนดสาธารณะ (ข้อมูลวัตถุ, โหนดถัดไป) {this.data = ข้อมูล; this.next = ถัดไป; } วัตถุสาธารณะ getData () {ส่งคืนข้อมูล; -ซึ่งหมายความว่าไม่ว่าเราจะประกาศโหนด <String> หรือโหนด <Integer>, JVM นั้นถือว่าเป็นโหนด<Poj็ก> ทั้งหมดในระหว่างการรันไทม์ มีวิธีแก้ปัญหานี้บ้างไหม? สิ่งนี้ต้องการให้เรารีเซ็ตขอบเขตตัวเองและแก้ไขรหัสด้านบนเป็นต่อไปนี้:
โหนดระดับสาธารณะ <t ขยายการเปรียบเทียบ <t>> {ข้อมูลส่วนตัว t; โหนดส่วนตัว <T> ถัดไป; โหนดสาธารณะ (ข้อมูล t, โหนด <t> ถัดไป) {this.data = data; this.next = ถัดไป; } สาธารณะ t getData () {ส่งคืนข้อมูล; -ด้วยวิธีนี้คอมไพเลอร์จะแทนที่สถานที่ที่ t ปรากฏขึ้นด้วยการเปรียบเทียบแทนวัตถุเริ่มต้น:
โหนดระดับสาธารณะ {ข้อมูลที่เปรียบเทียบได้ส่วนตัว; โหนดส่วนตัวถัดไป; โหนดสาธารณะ (ข้อมูลเปรียบเทียบโหนดถัดไป) {this.data = ข้อมูล; this.next = ถัดไป; } สาธารณะเทียบได้ getData () {return data; -แนวคิดข้างต้นอาจเข้าใจได้ง่ายขึ้น แต่ในความเป็นจริงการลบทั่วไปทำให้เกิดปัญหามากขึ้น จากนั้นมาดูอย่างเป็นระบบเกี่ยวกับปัญหาบางอย่างที่เกิดจากการลบประเภท ปัญหาบางอย่างอาจไม่พบใน C ++ ทั่วไป แต่คุณต้องระมัดระวังเป็นพิเศษใน Java
คำถามที่ 1
ไม่อนุญาตให้ใช้อาร์เรย์ทั่วไปใน Java หากคอมไพเลอร์ทำสิ่งต่อไปนี้มันจะรายงานข้อผิดพลาด:
รายการ <จำนวนเต็ม> [] arrayoflists = รายการใหม่ <integer> [2]; // ข้อผิดพลาดในการรวบรวมเวลา
ทำไมคอมไพเลอร์ไม่สนับสนุนการปฏิบัติข้างต้น? ใช้การคิดย้อนกลับต่อไปเราพิจารณาปัญหานี้จากมุมมองของคอมไพเลอร์
ก่อนอื่นดูตัวอย่างต่อไปนี้:
Object [] strings = สตริงใหม่ [2]; strings [0] = "hi"; // okstrings [1] = 100; // arraystoreexception ถูกโยนลงไป
รหัสข้างต้นเข้าใจง่าย สตริงอาร์เรย์ไม่สามารถจัดเก็บองค์ประกอบจำนวนเต็มและข้อผิดพลาดดังกล่าวมักจะต้องค้นพบจนกว่ารหัสจะทำงานและคอมไพเลอร์ไม่สามารถจดจำได้ ถัดไปมาดูกันว่าจะเกิดอะไรขึ้นถ้า Java สนับสนุนการสร้างอาร์เรย์ทั่วไป:
Object [] StringLists = รายการใหม่ <String> []; // ข้อผิดพลาดของคอมไพเลอร์ แต่แกล้งทำเป็นว่า stringlists [0] = new ArrayList <String> (); // ok // arraystoreexception ควรถูกโยนทิ้ง แต่รันไทม์ไม่สามารถตรวจจับได้ stringlists [1] = arraylist ใหม่ <integer> ();
สมมติว่าเราสนับสนุนการสร้างอาร์เรย์ทั่วไป เนื่องจากข้อมูลประเภทในระหว่างการรันไทม์ได้ถูกลบออก JVM จึงไม่ทราบความแตกต่างระหว่าง ArrayList ใหม่ <String> () และ ArrayList ใหม่ <Integer> () เลย หากข้อผิดพลาดดังกล่าวเกิดขึ้นในสถานการณ์การใช้งานจริงพวกเขาจะตรวจจับได้ยากมาก
หากคุณยังสงสัยเรื่องนี้อยู่คุณสามารถลองใช้รหัสต่อไปนี้:
ระดับสาธารณะ ERASEDTYPEEQUIVALENCE {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {คลาส C1 = arrayList ใหม่ <String> (). getClass (); คลาส C2 = arrayList ใหม่ <integer> (). getClass (); System.out.println (C1 == C2); // จริง }}คำถามที่ 2
นำคลาสโหนดของเรากลับมาใช้ใหม่ด้านบน สำหรับรหัสทั่วไปคอมไพเลอร์ Java จะช่วยเราใช้วิธีการบริดจ์
โหนดระดับสาธารณะ <T> {ข้อมูลสาธารณะ t; โหนดสาธารณะ (ข้อมูล t) {this.data = ข้อมูล; } โมฆะสาธารณะ setData (t data) {system.out.println ("node.setData"); this.data = ข้อมูล; }} คลาสสาธารณะ MyNode ขยายโหนด <integer> {public mynode (ข้อมูลจำนวนเต็ม) {super (data); } โมฆะสาธารณะ setData (ข้อมูลจำนวนเต็ม) {system.out.println ("mynode.setData"); super.setData (ข้อมูล); -หลังจากอ่านการวิเคราะห์ข้างต้นคุณอาจคิดว่าหลังจากการลบประเภทคอมไพเลอร์จะเปลี่ยนโหนดและ mynode เป็นต่อไปนี้:
โหนดระดับสาธารณะ {ข้อมูลวัตถุสาธารณะ; โหนดสาธารณะ (ข้อมูลวัตถุ) {this.data = ข้อมูล; } โมฆะสาธารณะ setData (ข้อมูลวัตถุ) {system.out.println ("node.setData"); this.data = ข้อมูล; }} คลาสสาธารณะ MyNode ขยายโหนด {public mynode (ข้อมูลจำนวนเต็ม) {super (data); } โมฆะสาธารณะ setData (ข้อมูลจำนวนเต็ม) {system.out.println ("mynode.setData"); super.setData (ข้อมูล); -ที่จริงแล้วนี่ไม่ใช่กรณี ก่อนอื่นดูรหัสต่อไปนี้ เมื่อรหัสนี้เรียกใช้ classcastexception จะถูกโยนออกแจ้งว่าสตริงไม่สามารถแปลงเป็นจำนวนเต็ม:
mynode mn = mynode ใหม่ (5); โหนด n = mn; // ประเภทดิบ - คอมไพเลอร์โยน warningn.setData ที่ไม่ได้ตรวจสอบ ("สวัสดี"); // ทำให้ classcastexception ถูกโยนลงไป// จำนวนเต็ม x = mn.data;หากเราทำตามรหัสที่เราสร้างขึ้นข้างต้นเราไม่ควรรายงานข้อผิดพลาดเมื่อทำงานไปยังบรรทัดที่ 3 (โปรดทราบว่าฉันแสดงความคิดเห็นบรรทัดที่ 4) เนื่องจากเมธอด setData (ข้อมูลสตริง) ไม่มีอยู่ใน MyNode ดังนั้นเราจึงสามารถเรียกใช้เมธอด setData (ข้อมูลวัตถุ) ของโหนดคลาสแม่ เนื่องจากด้วยวิธีนี้รหัสบรรทัด 3 ข้างต้นไม่ควรรายงานข้อผิดพลาดเนื่องจากสตริงแน่นอนสามารถแปลงเป็นวัตถุได้ดังนั้น ClassCastException จึงถูกโยนลงอย่างไร
อันที่จริงคอมไพเลอร์ Java จัดการรหัสข้างต้นโดยอัตโนมัติ:
คลาส MyNode ขยายโหนด {// Bridge วิธีการที่สร้างขึ้นโดยโมฆะสาธารณะของคอมไพเลอร์ setData (ข้อมูลวัตถุ) {setData ((จำนวนเต็ม) ข้อมูล); } โมฆะสาธารณะ setData (ข้อมูลจำนวนเต็ม) {system.out.println ("mynode.setData"); super.setData (ข้อมูล); -นี่คือเหตุผลที่มีการรายงานข้อผิดพลาดข้างต้น เมื่อ setData ((จำนวนเต็ม) ข้อมูล); สตริงไม่สามารถแปลงเป็นจำนวนเต็มได้ ดังนั้นเมื่อคอมไพเลอร์แจ้งเตือนคำเตือนที่ไม่ถูกตรวจสอบในบรรทัดที่ 2 ด้านบนเราไม่สามารถเลือกที่จะเพิกเฉยได้มิฉะนั้นเราจะต้องรอจนกว่าจะถึงเวลาทำงานเพื่อค้นหาข้อยกเว้น มันจะดีถ้าเราเพิ่มโหนด <integer> n = mn ในตอนแรกเพื่อให้คอมไพเลอร์สามารถช่วยเราค้นหาข้อผิดพลาดล่วงหน้า
คำถามที่ 3
ดังที่เราได้กล่าวไว้ข้างต้น Java Generics สามารถให้การตรวจสอบประเภทคงที่ในระดับใหญ่เท่านั้นจากนั้นข้อมูลประเภทจะถูกลบดังนั้นคอมไพเลอร์จะไม่ผ่านวิธีการใช้พารามิเตอร์ประเภทต่อไปนี้เพื่อสร้างอินสแตนซ์:
สาธารณะคงที่ <E> ต่อผนวกเป็นโมฆะ (รายการ <e> รายการ) {e elem = new e (); // Compile-Time Error list.add (elem);}แต่เราควรทำอย่างไรถ้าเราต้องการสร้างอินสแตนซ์โดยใช้พารามิเตอร์ประเภทในบางสถานการณ์ การสะท้อนสามารถใช้เพื่อแก้ปัญหานี้:
สาธารณะคงที่ <E> ต่อผนวกเป็นโมฆะ (รายการ <E> รายการ, คลาส <e> CLS) โยนข้อยกเว้น {e elem = cls.NewInstance (); // ok list.add (elem);}เราเรียกได้ว่าแบบนี้:
รายการ <String> ls = arrayList ใหม่ <> (); ผนวก (ls, string.class);
ในความเป็นจริงสำหรับปัญหาข้างต้นคุณยังสามารถใช้รูปแบบการออกแบบโรงงานและเทมเพลตเพื่อแก้ปัญหา เพื่อนที่สนใจอาจต้องการดูคำอธิบายของการสร้างอินสแตนซ์ของประเภทในบทที่ 15 ในการคิดใน Java เราจะไม่เข้าไปที่นี่
คำถามที่ 4
เราไม่สามารถใช้คำหลักของอินสแตนซ์ของรหัสทั่วไปได้โดยตรงเนื่องจากคอมไพเลอร์ Java จะลบข้อมูลประเภททั่วไปที่เกี่ยวข้องทั้งหมดทั้งหมดเมื่อสร้างรหัสเช่นเดียวกับ JVM ที่เราตรวจสอบข้างต้นไม่สามารถรับรู้ถึงความแตกต่างระหว่าง ArrayList <integer> และ ArrayList <String> ในระหว่างการรันไทม์:
สาธารณะคงที่ <E> เป็นโมฆะ rtti (รายการ <e> รายการ) {ถ้า (รายการอินสแตนซ์ของ arraylist <integer>) {// ข้อผิดพลาดคอมไพล์-เวลา // ... }} => {arrayList <integer>, arrayList <string>, linkedListดังกล่าวข้างต้นเราสามารถใช้ WildCards เพื่อรีเซ็ตขอบเขตเพื่อแก้ปัญหานี้:
โมฆะสาธารณะคงที่ rtti (รายการ <?> รายการ) {ถ้า (รายการอินสแตนซ์ของ ArrayList <?>) {// ตกลง; Instanceof ต้องการประเภท // ... }} reifiable reifiableสรุป
ข้างต้นคือทั้งหมดที่เกี่ยวกับการเข้าใจ Java Generics อีกครั้งในบทความนี้ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงเว็บไซต์นี้ต่อไปได้:
คำอธิบายโดยละเอียดเกี่ยวกับพื้นฐานอาร์เรย์ Java
พื้นฐานของการเขียนโปรแกรม Java: การเลียนแบบการแชร์รหัสผู้ใช้
พื้นฐานของการเขียนโปรแกรมเครือข่าย Java: การสื่อสารทางเดียว
หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น ขอบคุณเพื่อนที่ให้การสนับสนุนเว็บไซต์นี้!