1. ทำไม - เหตุผลในการแนะนำกลไกทั่วไป
หากเราต้องการใช้อาร์เรย์สตริงและต้องการให้เปลี่ยนขนาดแบบไดนามิกเราทุกคนจะนึกถึงการใช้ ArrayList เพื่อรวมวัตถุสตริง อย่างไรก็ตามหลังจากผ่านไประยะหนึ่งเราต้องการใช้อาร์เรย์ของวัตถุวันที่ที่สามารถเปลี่ยนขนาดได้ ในเวลานี้เราหวังว่าจะสามารถนำการใช้งาน ArrayList มาใช้ใหม่สำหรับวัตถุสตริงที่ฉันเขียนไว้ก่อนหน้านี้
ก่อนหน้า Java 5 การดำเนินการของ ArrayList มีดังนี้:
ArrayList คลาสสาธารณะ {วัตถุสาธารณะได้รับ (int i) {... } โมฆะสาธารณะเพิ่ม (วัตถุ o) {... } ... วัตถุส่วนตัว [] elementData;}จากรหัสด้านบนเราจะเห็นว่าฟังก์ชั่นเพิ่มที่ใช้เพื่อเพิ่มองค์ประกอบใน ArrayList จะได้รับพารามิเตอร์ประเภทวัตถุ วิธีการรับที่ได้รับองค์ประกอบที่ระบุจาก ArrayList ยังส่งคืนวัตถุประเภทวัตถุ อาร์เรย์วัตถุอาร์เรย์ ElementData จัดเก็บวัตถุใน ArrayList กล่าวคือไม่ว่าคุณจะใส่ประเภทประเภทใดลงใน ArrayList มันเป็นวัตถุวัตถุภายใน
การใช้งานทั่วไปตามการสืบทอดจะทำให้เกิดปัญหาสองประการ: คำถามแรกเกี่ยวกับวิธีการรับ ทุกครั้งที่เราเรียกวิธีการรับเราจะส่งคืนวัตถุวัตถุและทุกครั้งที่เราต้องใช้ประเภทของประเภทที่เราต้องการซึ่งจะดูลำบากมาก คำถามที่สองเกี่ยวกับวิธีการเพิ่ม หากเราเพิ่มวัตถุไฟล์ไปยัง ArrayList ที่รวมวัตถุสตริงคอมไพเลอร์จะไม่สร้างพรอมต์ข้อผิดพลาดใด ๆ ซึ่งไม่ใช่สิ่งที่เราต้องการ
ดังนั้นการเริ่มต้นจาก Java 5 สามารถใช้ ArrayList เพื่อเพิ่มพารามิเตอร์ประเภท (ประเภทพารามิเตอร์) เมื่อใช้งาน พารามิเตอร์ประเภทนี้ใช้เพื่อระบุประเภทองค์ประกอบใน ArrayList การแนะนำของพารามิเตอร์ประเภทจะช่วยแก้ปัญหาทั้งสองที่กล่าวถึงข้างต้นดังแสดงในรหัสต่อไปนี้:
arrayList <String> s = new ArrayList <String> (); s.add ("abc"); string s = s.get (0); // ไม่จำเป็นต้องโยน s.add (123); // ข้อผิดพลาดในการรวบรวมคุณสามารถเพิ่มวัตถุสตริงลงไปได้เท่านั้น ...ในรหัสข้างต้นหลังจากคอมไพเลอร์ "รู้" สตริงพารามิเตอร์ประเภทของ ArrayList มันจะเสร็จสิ้นการคัดเลือกนักแสดงและพิมพ์ให้เรา
2. ชั้นเรียนทั่วไป
คลาสทั่วไปที่เรียกว่าเป็นคลาสที่มีพารามิเตอร์ประเภทหนึ่งหรือมากกว่า ตัวอย่างเช่น:
คู่ระดับสาธารณะ <t, u> {ส่วนตัว t ก่อน; ส่วนตัวคุณสอง; คู่สาธารณะ (t แรก, U ที่สอง) {this.first = ก่อน; this.second = วินาที; } สาธารณะ t getFirst () {return ก่อน; } สาธารณะ u getsecond () {return second; } โมฆะสาธารณะ setFirst (t newValue) {first = newValue; -ในรหัสด้านบนเราจะเห็นได้ว่าพารามิเตอร์ประเภทของคู่คลาสทั่วไปคือ T และ U และวางไว้ในวงเล็บมุมหลังจากชื่อคลาส ที่นี่ T หมายถึงตัวอักษรตัวแรกของประเภทซึ่งแสดงถึงประเภท ที่ใช้กันทั่วไปคือ e (องค์ประกอบ), k (คีย์), v (ค่า) ฯลฯ แน่นอนว่ามันยังดีอย่างสมบูรณ์แบบที่จะไม่ใช้ตัวอักษรเหล่านี้เพื่ออ้างถึงพารามิเตอร์ประเภท
เมื่ออินสแตนซ์คลาสทั่วไปเราจำเป็นต้องแทนที่พารามิเตอร์ประเภทด้วยประเภทเฉพาะเช่นการสร้างอินสแตนซ์คลาส <t, u> เราสามารถทำได้:
จับคู่ <สตริง, จำนวนเต็ม> pair = คู่ใหม่ <สตริง, จำนวนเต็ม> ();
3. วิธีการทั่วไป
วิธีการทั่วไปที่เรียกว่าเป็นวิธีที่มีพารามิเตอร์ประเภท มันสามารถกำหนดในคลาสทั่วไปหรือคลาสปกติ ตัวอย่างเช่น:
คลาสสาธารณะ Arrayalg {สาธารณะคงที่ <t> t getMiddle (t [] a) {return a [a.length / 2]; -วิธี getMiddle ในรหัสด้านบนเป็นวิธีทั่วไปและรูปแบบที่กำหนดคือตัวแปรประเภทจะถูกวางไว้หลังจากตัวดัดแปลงและก่อนประเภทการส่งคืน เราจะเห็นได้ว่าวิธีการทั่วไปข้างต้นสามารถเรียกได้สำหรับอาร์เรย์ประเภทต่างๆ เมื่อประเภทของอาร์เรย์เหล่านี้เป็นที่รู้จักกันว่ามี จำกัด แม้ว่าพวกเขาจะสามารถนำไปใช้กับการโอเวอร์โหลดได้ประสิทธิภาพการเข้ารหัสจะต่ำกว่ามาก รหัสตัวอย่างสำหรับการเรียกวิธีการทั่วไปด้านบนมีดังนี้:
String [] strings = {"aa", "bb", "cc"};
String middle = arrayalg.getMiddle (ชื่อ);
4. ข้อ จำกัด ของตัวแปรประเภท
ในบางกรณีคลาสทั่วไปหรือวิธีการทั่วไปต้องการ จำกัด พารามิเตอร์ประเภทของพวกเขาเพิ่มเติม ตัวอย่างเช่นหากเราต้องการกำหนดพารามิเตอร์ประเภทที่สามารถเป็นคลาสย่อยของคลาสที่แน่นอนหรือคลาสเฉพาะที่ใช้อินเทอร์เฟซที่แน่นอน ไวยากรณ์ที่เกี่ยวข้องมีดังนี้:
<t ขยายขอบเขตการใช้งาน> (BoundingType เป็นคลาสหรืออินเทอร์เฟซ) อาจมีมากกว่า 1 boundingType เพียงแค่ใช้ "&" เพื่อเชื่อมต่อ
5. เข้าใจการใช้งานทั่วไป
ในความเป็นจริงจากมุมมองของเครื่องเสมือนไม่มีแนวคิดของ "ยาสามัญ" ตัวอย่างเช่นคู่คลาสทั่วไปที่เรากำหนดไว้ข้างต้นมีลักษณะเช่นนี้ในเครื่องเสมือน (นั่นคือหลังจากถูกรวบรวมเป็น bytecode):
คู่ระดับสาธารณะ {วัตถุส่วนตัวก่อน; วัตถุส่วนตัวที่สอง; คู่สาธารณะ (วัตถุแรกวัตถุที่สอง) {this.first = ก่อน; this.second = วินาที; } วัตถุสาธารณะ getFirst () {return ก่อน; } วัตถุสาธารณะ getSecond () {return second; } โมฆะสาธารณะ setFirst (Object newValue) {first = newValue; } โมฆะสาธารณะ setSecond (Object newValue) {second = newValue; -คลาสข้างต้นนั้นได้มาจากการลบประเภทและเป็นประเภทดิบที่สอดคล้องกับคลาสทั่วไป การลบประเภทหมายถึงการแทนที่พารามิเตอร์ประเภททั้งหมดด้วย boundingType (แทนที่ด้วยวัตถุหากไม่มีการเพิ่มข้อ จำกัด )
เราสามารถตรวจสอบได้ว่าหลังจากรวบรวม pair.java พิมพ์ "javap -c -c -s pair" เพื่อรับ:
บรรทัดที่มี "descriptor" ในรูปด้านบนคือลายเซ็นของวิธีที่สอดคล้องกัน ตัวอย่างเช่นจากบรรทัดที่สี่เราจะเห็นได้ว่าพารามิเตอร์ที่เป็นทางการทั้งสองของตัวสร้างคู่ได้กลายเป็นวัตถุหลังจากการลบประเภท
เนื่องจากคู่คลาสทั่วไปกลายเป็นประเภทดิบในเครื่องเสมือนวิธีการ getFirst จะส่งคืนวัตถุวัตถุและจากมุมมองของคอมไพเลอร์วิธีนี้จะส่งคืนวัตถุของพารามิเตอร์ประเภทที่ระบุเมื่อเราอินสแตนซ์คลาส ในความเป็นจริงมันเป็นคอมไพเลอร์ที่ช่วยให้เราทำงานคัดเลือกนักแสดงให้เสร็จ กล่าวอีกนัยหนึ่งคอมไพเลอร์จะแปลงการโทรเป็นวิธีการ getFirst ในคลาส GENINIC GENERIC เป็นคำแนะนำเครื่องเสมือนจริงสองคำ:
ครั้งแรกคือการโทรไปยังวิธีการประเภทดิบ getfirst ซึ่งส่งคืนวัตถุวัตถุ คำสั่งที่สองหล่อวัตถุวัตถุที่ส่งคืนไปยังประเภทพารามิเตอร์ประเภทที่เราระบุ
การลบประเภทยังเกิดขึ้นในวิธีการทั่วไปเช่นวิธีการทั่วไปต่อไปนี้:
สาธารณะคงที่ <t ขยายการเปรียบเทียบ> t นาที (t [] a)
หลังจากการรวบรวมมันจะกลายเป็นเช่นนี้หลังจากการลบประเภท:
ค่าคงที่สาธารณะเทียบเคียงสาธารณะ (เทียบเท่า [] A)
พิมพ์วิธีการลบวิธีการอาจทำให้เกิดปัญหาบางอย่างพิจารณารหัสต่อไปนี้:
คลาส DateInterval ขยายคู่ <วันที่, วันที่> {โมฆะสาธารณะ setSecond (วันที่ที่สอง) {ถ้า (second.Compareto (getFirst ())> = 0) {super.setSecond (วินาที); -หลังจากที่รหัสด้านบนถูกลบตามประเภทมันจะกลายเป็น:
คลาส DateInterval ขยายคู่ {โมฆะสาธารณะ setSecond (วันที่ที่สอง) {... } ... }ในคลาส DateInterval นอกจากนี้ยังมีวิธีการ setsecond ที่สืบทอดมาจากคลาสคู่ (หลังจากการลบประเภท) ดังนี้:
โมฆะสาธารณะ setsecond (วัตถุที่สอง)
ตอนนี้เราจะเห็นได้ว่าวิธีนี้มีลายเซ็นวิธีการที่แตกต่างกัน (พารามิเตอร์ที่เป็นทางการที่แตกต่างกัน) จากวิธี setSecond ที่ถูกแทนที่ด้วย dateInterval ดังนั้นจึงเป็นสองวิธีที่แตกต่างกันอย่างไรก็ตามวิธีทั้งสองนี้ไม่ควรเป็นวิธีที่แตกต่างกัน (เพราะมันแทนที่) พิจารณารหัสต่อไปนี้:
ช่วงวันที่ DateInterval = new DateInterval (... ); pair <วันที่, วันที่> pair = interval; date adate = วันที่ใหม่ (... ); pair.setSecond (Adate);
จากรหัสข้างต้นเราจะเห็นว่าคู่นั้นอ้างอิงถึงวัตถุวันที่จริงดังนั้นควรเรียกใช้วิธีการตั้งค่าของวันที่ของวันที่ ปัญหาที่นี่คือการลบความขัดแย้งกับความหลากหลาย
มาเรียงกันว่าทำไมปัญหานี้เกิดขึ้น: ก่อนหน้านี้ถูกประกาศว่าเป็นคู่ประเภท <วันที่วันที่> และคลาสนี้ดูเหมือนจะมีวิธีการ "setSecond (วัตถุ)" เพียงหนึ่งวิธีในเครื่องเสมือน ดังนั้นเมื่อทำงานเครื่องเสมือนจะค้นพบว่าทั้งคู่อ้างอิงถึงวัตถุวันที่มันจะเรียกว่า "setSecond (วัตถุ)" ของวันที่ DateInterval แต่มีเพียงเมธอด "setSecond (วันที่)" ในคลาสวันที่
การแก้ปัญหานี้คือการสร้างวิธีการบริดจ์ใน DateInterval โดยคอมไพเลอร์:
โมฆะสาธารณะ setSecond (วัตถุที่สอง) {setSecond ((วันที่) ที่สอง);}6. สิ่งที่ควรทราบ
(1) พารามิเตอร์ประเภทไม่สามารถสร้างอินสแตนซ์ด้วยประเภทพื้นฐาน
นั่นคือข้อความต่อไปนี้ผิดกฎหมาย:
pair <int, int> pair = คู่ใหม่ <int, int> ();
อย่างไรก็ตามเราสามารถใช้ประเภทบรรจุภัณฑ์ที่เกี่ยวข้องแทน
(2) ไม่สามารถโยนหรือจับอินสแตนซ์คลาสทั่วไป
การขยายชั้นเรียนทั่วไปที่โยนได้นั้นผิดกฎหมายดังนั้นอินสแตนซ์ชั้นเรียนทั่วไปจึงไม่สามารถโยนหรือถูกจับได้ แต่มันถูกกฎหมายที่จะใช้พารามิเตอร์ประเภทในการประกาศข้อยกเว้น:
สาธารณะคงที่ <t ขยายได้> void dowork (t t) พ่น t {ลอง {... } catch (throwable realcause) {t.initcause (realcause); โยน t; -(3) อาร์เรย์พารามิเตอร์ผิดกฎหมาย
ใน Java อาร์เรย์วัตถุ [] สามารถเป็นคลาสแม่ของอาร์เรย์ใด ๆ (เนื่องจากอาร์เรย์ใด ๆ สามารถแปลงขึ้นไปเป็นอาร์เรย์ของคลาสพาเรนต์ที่ระบุประเภทองค์ประกอบเมื่อกำหนด) พิจารณารหัสต่อไปนี้:
สตริง [] strs = สตริงใหม่ [10]; วัตถุ [] objs = strs; obj [0] = วันที่ใหม่ (... );
ในรหัสข้างต้นเรากำหนดองค์ประกอบอาร์เรย์ให้กับวัตถุที่เป็นไปตามประเภทคลาสแม่ (วัตถุ) แต่ไม่เหมือนกับประเภทดั้งเดิม (คู่) มันสามารถผ่านเวลาคอมไพล์ได้
จากเหตุผลข้างต้นสมมติว่า Java ช่วยให้เราสามารถประกาศและเริ่มต้นอาร์เรย์ทั่วไปผ่านคำสั่งต่อไปนี้:
จับคู่ <สตริงสตริง> [] คู่ = คู่ใหม่ <สตริงสตริง> [10];
จากนั้นหลังจากเครื่องเสมือนทำการลบประเภทคู่จริงกลายเป็นคู่ [] อาร์เรย์และเราสามารถแปลงมันขึ้นไปเป็นอาร์เรย์ [] อาร์เรย์ ในเวลานี้ถ้าเราเพิ่มคู่ <วันที่วันที่> วัตถุลงไปเราสามารถผ่านการตรวจสอบเวลารวบรวมและตรวจสอบเวลาทำงาน ความตั้งใจดั้งเดิมของเราคือปล่อยให้อาร์เรย์จัดเก็บคู่ <สตริงสตริง> วัตถุซึ่งจะทำให้ยากต่อการค้นหาข้อผิดพลาด ดังนั้น Java ไม่อนุญาตให้เราประกาศและเริ่มต้นอาร์เรย์ทั่วไปผ่านแบบฟอร์มคำสั่งข้างต้น
สามารถประกาศและเริ่มต้นอาร์เรย์ทั่วไปโดยใช้คำสั่งต่อไปนี้:
pair <string, string> [] pairs = (pair <string, string> []) คู่ใหม่ [10];
(4) ตัวแปรประเภทไม่สามารถสร้างอินสแตนซ์ได้
ไม่สามารถใช้ตัวแปรประเภทในรูปแบบเช่น "ใหม่ t (... )", "ใหม่ t [... ]", "t.class" เหตุผลที่ Java ห้ามไม่ให้เราทำสิ่งนี้เป็นเรื่องง่าย เนื่องจากมีการลบประเภทข้อความเช่น "ใหม่ t (... )" จะกลายเป็น "วัตถุใหม่ (... )" ซึ่งมักจะไม่ใช่สิ่งที่เราหมายถึง เราสามารถแทนที่การโทรเป็น "ใหม่ t [... ]" ด้วยคำสั่งต่อไปนี้:
อาร์เรย์ = (t []) วัตถุใหม่ [n];
(5) ประเภทตัวแปรไม่สามารถใช้ในบริบทคงที่ของคลาสทั่วไป
โปรดทราบว่าเราเน้นคลาสทั่วไปที่นี่ เนื่องจากวิธีการทั่วไปแบบคงที่สามารถกำหนดได้ในคลาสสามัญเช่นวิธี getMiddle ในคลาส Arrayalg ที่กล่าวถึงข้างต้น ด้วยเหตุผลดังกล่าวโปรดพิจารณารหัสต่อไปนี้:
คนชั้นเรียนสาธารณะ <T> {ชื่อสาธารณะคงที่ สาธารณะคงที่ t getName () {... }}เรารู้ว่าในเวลาเดียวกันอาจมีอินสแตนซ์ของคลาส <t> ในหน่วยความจำมากกว่าหนึ่งคน สมมติว่ามีวัตถุ <String> วัตถุและผู้คน <teger> วัตถุในหน่วยความจำในขณะนี้และตัวแปรคงที่และวิธีการคงที่ของคลาสจะถูกแชร์โดยอินสแตนซ์คลาสทั้งหมด ดังนั้นคำถามคือประเภทสตริงชื่อหรือประเภทจำนวนเต็มหรือไม่? ด้วยเหตุผลนี้ไม่อนุญาตให้ใช้ตัวแปรประเภทใน Java ในบริบทคงที่ของคลาสทั่วไป
7. พิมพ์ไวด์การ์ด
ก่อนที่จะแนะนำไวด์การ์ดประเภทแรกแนะนำสองคะแนน:
(1) สมมติว่านักเรียนเป็นคลาสย่อยของผู้คน แต่คู่ <นักเรียนนักเรียน> ไม่ได้เป็นคลาสย่อยของคู่ <คนคน> และไม่มีความสัมพันธ์ระหว่างพวกเขา
(2) มีความสัมพันธ์ "is-a" ระหว่าง pair <t, t> และคู่ประเภทดั้งเดิม จับคู่ <t, t> สามารถแปลงเป็นประเภทจับคู่ในทุกกรณี
ตอนนี้พิจารณาวิธีนี้:
Public Static Void PrintName (Pair <People, People> P) {People P1 = P.GetFirst (); System.out.println (p1.getName ()); // สมมติว่าคลาสคนกำหนดวิธีการอินสแตนซ์ getName}ในวิธีการข้างต้นเราต้องการที่จะสามารถผ่านพารามิเตอร์ของคู่ <นักเรียนนักเรียน> และคู่ <คนผู้คน> ในเวลาเดียวกัน แต่ไม่มีความสัมพันธ์ "IS-A" ระหว่างทั้งสอง ในกรณีนี้ Java ให้วิธีแก้ปัญหาแก่เรา: ใช้ Pair <? ขยายคน> เป็นประเภทของพารามิเตอร์ที่เป็นทางการ กล่าวคือจับคู่ <นักเรียนนักเรียน> และคู่ <คนผู้คน> ทั้งคู่ได้รับการยกย่องว่าเป็นคลาสย่อยของคู่ <? ขยายคน>.
รหัสที่ดูเหมือน "<? ขยายขอบเขต>" เรียกว่าข้อ จำกัด ชนิดย่อยของอักขระไวลด์การ์ด สอดคล้องกับสิ่งนี้คือข้อ จำกัด ประเภทสุดยอดของอักขระไวด์การ์ดรูปแบบมีดังนี้: <? Super BoundingType>
ตอนนี้ลองพิจารณารหัสต่อไปนี้:
คู่ <student> นักเรียน = คู่ใหม่ <student> (Student1, Student2); Pair <? ขยายคน> wildchards = นักเรียน; wildchards.setfirst (คน 1);
บรรทัดที่สามของรหัสด้านบนจะรายงานข้อผิดพลาดเนื่องจาก Wildchards เป็นคู่ <? ขยายผู้คน> วัตถุและวิธีการ setfirst และวิธีการ getFirst มีดังนี้:
เป็นโมฆะ setfirst (? ขยายคน)? ขยายคน getfirst ()
สำหรับวิธีการ SetFirst คอมไพเลอร์จะไม่ทราบว่าพารามิเตอร์อย่างเป็นทางการประเภทใด (รู้ว่าเป็นคลาสย่อยของคนเท่านั้น) เมื่อเราพยายามส่งผ่านวัตถุของคนคอมไพเลอร์ไม่สามารถระบุได้ว่าผู้คนและพารามิเตอร์ที่เป็นทางการนั้นเป็น "IS-A" หรือไม่ดังนั้นการเรียกใช้วิธี SetFirst จะรายงานข้อผิดพลาดหรือไม่ มันถูกกฎหมายที่จะเรียกวิธีการ getfirst ของ Wildchards เพราะเรารู้ว่ามันจะส่งคืนคลาสย่อยของผู้คนและคลาสย่อยของผู้คน "เป็นคนเสมอ" (คุณสามารถแปลงวัตถุคลาสย่อยเป็นวัตถุหลักได้ตลอดเวลา)
ในกรณีของข้อ จำกัด ของ Wildcard Supertype การเรียกวิธีการ Getter นั้นผิดกฎหมายในขณะที่วิธีการโทร Setter นั้นถูกกฎหมาย
นอกเหนือจากข้อ จำกัด ย่อยและข้อ จำกัด supertype แล้วยังมีตัวแทนที่เรียกว่า Infinite Wildcard ซึ่งมีลักษณะเช่นนี้: <?> เราจะใช้สิ่งนี้เมื่อใด พิจารณาสถานการณ์นี้ เมื่อเราเรียกวิธีการเราจะส่งคืนวิธีการ getPairs ซึ่งจะส่งคืนชุดคู่ <t, t> วัตถุ ในหมู่พวกเขาคือคู่ <นักเรียนนักเรียน> และคู่ <ครูครู> วัตถุ (ไม่มีความสัมพันธ์ที่สืบทอดระหว่างชั้นเรียนนักเรียนและชั้นเรียนครู) อย่างชัดเจนในกรณีนี้ทั้งข้อ จำกัด ย่อยและข้อ จำกัด supertype ไม่สามารถใช้ได้ ในเวลานี้เราสามารถใช้คำสั่งนี้เพื่อแก้ไขได้:
จับคู่ <?> [] คู่ = getPairs (... );