1. สิ่งที่เป็นทั่วไปคืออะไร?
ก่อนที่จะพูดคุยเกี่ยวกับการอนุมานประเภทเราต้องตรวจสอบสิ่งที่เป็นทั่วไป ยาสามัญเป็นคุณสมบัติใหม่ของ Java SE 1.5 Essence of Generics เป็นประเภทพารามิเตอร์นั่นคือประเภทข้อมูลที่ดำเนินการถูกระบุเป็นพารามิเตอร์ ในแง่ของคนธรรมดามันจะเป็น "ตัวแปรประเภท" ตัวแปรประเภทนี้สามารถใช้ในการสร้างคลาสอินเตอร์เฟสและวิธีการ วิธีที่ง่ายที่สุดในการทำความเข้าใจ Java Generics คือการพิจารณาว่าเป็นไวยากรณ์ที่สะดวกซึ่งสามารถช่วยคุณได้บ้างใน casting แสดงประเภท Java:
รายการ <apple> box = new ArrayList <Apple> (); box.add (ใหม่ Apple ()); Apple Apple = Box.get (0);
รหัสด้านบนแสดงอย่างชัดเจน: กล่องเป็น List ที่มีวัตถุ Apple เมธอด get ส่งคืนอินสแตนซ์ของวัตถุ Apple และกระบวนการนี้ไม่จำเป็นต้องมีการแปลงประเภท ไม่มียาสามัญต้องเขียนรหัสข้างต้นเช่นนี้:
Apple = (Apple) Box.get (0);
แน่นอนว่ายาสามัญนั้นไม่ง่ายอย่างที่ฉันอธิบายไว้ที่นี่ แต่นี่ไม่ใช่ตัวเอกของวันของเรา นักเรียนที่ไม่เข้าใจยาชื่อสามัญต้องทำอย่างดีสำหรับบทเรียน ~ แน่นอนว่าสื่ออ้างอิงที่ดีที่สุดยังคงเป็นเอกสารอย่างเป็นทางการ
2. ปัญหาที่เกิดจากทั่วไป (ก่อน Java 7)
ข้อได้เปรียบที่ใหญ่ที่สุดของยาชื่อสามัญคือพวกเขาให้ความปลอดภัยประเภทของโปรแกรมและสามารถย้อนหลังได้ อย่างไรก็ตามยังมีสิ่งต่าง ๆ ที่ทำให้นักพัฒนาไม่มีความสุข ประเภทของยาสามัญจะต้องเขียนทุกครั้งที่พวกเขากำหนด ข้อมูลจำเพาะการแสดงผลนี้ไม่เพียง แต่ให้ความรู้สึกว่าเป็นคำฟา้นิเตอร์ แต่ที่สำคัญที่สุดคือโปรแกรมเมอร์จำนวนมากไม่คุ้นเคยกับยาชื่อสามัญดังนั้นพวกเขาจึงไม่สามารถให้พารามิเตอร์ประเภทที่ถูกต้องในหลายกรณี ตอนนี้คอมไพเลอร์จะทำให้พารามิเตอร์ประเภทของยาชื่อสามัญโดยอัตโนมัติซึ่งสามารถลดสถานการณ์นี้และปรับปรุงความสามารถในการอ่านรหัส
3. การปรับปรุงประเภทการสืบทอดของยาชื่อสามัญใน Java 7
การใช้ประเภททั่วไปใน Java 7 เวอร์ชันก่อนหน้านี้จำเป็นต้องเพิ่มประเภททั่วไปทั้งสองด้านเมื่อประกาศและกำหนดค่า ตัวอย่างเช่น:
แผนที่ <สตริง, จำนวนเต็ม> map = new hashmap <string, integer> ();
หลายคนต้องเป็นเหมือนฉันในตอนแรกและพวกเขาก็งงงวยกับสิ่งนี้: ฉันไม่ได้ประกาศประเภทพารามิเตอร์ในการประกาศตัวแปรหรือไม่? ทำไมฉันถึงต้องเขียนมันออกมาเมื่อวัตถุเริ่มต้น? นี่คือสิ่งที่ทำให้นายสามัญบ่นเมื่อพวกเขาปรากฏตัวครั้งแรก อย่างไรก็ตามเป็นเรื่องน่ายินดีที่ในขณะที่ Java กำลังปรับปรุงนักออกแบบยังปรับปรุงคอมไพเลอร์ Java อย่างต่อเนื่องเพื่อให้ฉลาดและมีมนุษยธรรมมากขึ้น นี่คือตัวเอกของเราในวันนี้: ประเภท pushdown ... อืม ... มันไม่ใช่ประเภทที่ได้มานั่นคือการอนุมานประเภท เมื่อผู้ชายคนนี้ปรากฏขึ้นเมื่อเขาเขียนโค้ดข้างต้นเขาสามารถละเว้นประเภทพารามิเตอร์ได้อย่างมีความสุขเมื่ออินสแตนซ์ของวัตถุมีการสร้างอินสแตนซ์และมันจะกลายเป็นเช่นนี้:
แผนที่ <สตริง, จำนวนเต็ม> map = new hashmap <> ();
ในคำสั่งนี้คอมไพเลอร์จะอนุมานประเภททั่วไปโดยอัตโนมัติเมื่อมีการสร้างอินสแตนซ์ HashMap ตามประเภททั่วไปเมื่อการประกาศตัวแปร อีกครั้งโปรดให้แน่ใจว่าได้ให้ความสนใจกับ "<>" ที่อยู่เบื้องหลัง new HashMap โดยการเพิ่ม "<>" นี้หมายความว่ามันเป็นการอนุมานประเภทอัตโนมัติมิฉะนั้นจะเป็น HashMap ไม่ใช่ generic และจะได้รับคำเตือนเมื่อรวบรวมซอร์สโค้ดโดยใช้คอมไพเลอร์ คู่ของวงเล็บมุมนี้ "<>" เรียกว่า "ไดมอนด์" ในเอกสารอย่างเป็นทางการ
อย่างไรก็ตามประเภทที่ได้มาในเวลานี้ยังไม่สมบูรณ์ (แม้กระทั่งผลิตภัณฑ์กึ่งสำเร็จรูป) เนื่องจากการอนุมานประเภทเมื่อสร้างอินสแตนซ์ทั่วไปใน Java SE 7 มี จำกัด : เฉพาะในกรณีที่ประเภทพารามิเตอร์ของตัวสร้างจะถูกประกาศอย่างมีนัยสำคัญในบริบทการอนุมานประเภทสามารถใช้งานได้ ตัวอย่างเช่น: ตัวอย่างต่อไปนี้ไม่สามารถรวบรวมได้อย่างถูกต้องใน Java 7 (แต่สามารถรวบรวมได้ใน Java 8 ตอนนี้เนื่องจากประเภททั่วไปจะอนุมานโดยอัตโนมัติตามพารามิเตอร์วิธี):
รายการ <String> list = new ArrayList <> (); list.add ("A"); // เนื่องจาก Addall คาดว่าจะได้รับพารามิเตอร์ของการรวบรวมประเภท <? ขยายสตริง> คำสั่งต่อไปนี้ไม่สามารถส่งผ่านรายการ ADDALL (arrayList ใหม่ <> ());4. การแก้ไขในชวา 8
ในเอกสาร Java อย่างเป็นทางการล่าสุดเราสามารถเห็นคำจำกัดความของประเภทที่ได้มา:
พิมพ์การอนุมานเป็นความสามารถของคอมไพเลอร์ Java ในการดูการเรียกใช้แต่ละวิธีและการประกาศที่สอดคล้องกันเพื่อกำหนดอาร์กิวเมนต์ประเภท (หรืออาร์กิวเมนต์) ที่ทำให้การเรียกใช้ อัลกอริทึมการอนุมานกำหนดประเภทของอาร์กิวเมนต์และหากมีประเภทที่ผลลัพธ์จะถูกกำหนดหรือส่งคืน ในที่สุดอัลกอริทึมการอนุมานพยายามค้นหาประเภทที่เฉพาะเจาะจงมากที่สุดที่ทำงานกับอาร์กิวเมนต์ทั้งหมด
ในระยะสั้นประเภทที่ได้มาหมายถึงความสามารถของคอมไพเลอร์เพื่อกำหนดประเภทพารามิเตอร์ที่ต้องการตามวิธีที่คุณเรียกและการประกาศที่สอดคล้องกัน และมีตัวอย่างในเอกสารอย่างเป็นทางการเพื่ออธิบาย:
คงที่ <t> t เลือก (t a1, t a2) {return a2; } serializable s = pick ("d", arraylist ใหม่ <String> ()); ที่นี่คอมไพเลอร์สามารถอนุมานได้ว่าประเภทของพารามิเตอร์ที่สองที่ผ่านในวิธี pick นั้นสามารถ Serializable
ในเวอร์ชัน Java ก่อนหน้านี้หากตัวอย่างข้างต้นสามารถรวบรวมได้คุณต้องเขียนสิ่งนี้:
serializable s = this. <serializable> เลือก ("D", arrayList ใหม่ <String> ());เหตุผลโดยละเอียดสำหรับการเขียนสิ่งนี้สามารถเห็นได้ในบททั่วไปของความคิดการเขียนโปรแกรม Java ของ Bruce Eckel (ฉบับที่สี่) แน่นอนว่าหนังสือเล่มนี้มีพื้นฐานมาจาก Java 6 และรุ่นนี้ไม่มีแนวคิดเกี่ยวกับประเภทที่ได้มา เมื่อเห็นสิ่งนี้หลายคนสามารถเห็นพลังของประเภทที่ได้มาอย่างชัดเจนในเวอร์ชันล่าสุด มันไม่ จำกัด เฉพาะกระบวนการประกาศและอินสแตนซ์ของคลาสทั่วไป แต่ขยายไปถึงวิธีการที่มีพารามิเตอร์ทั่วไป
4.1 การอนุมานประเภทและวิธีการทั่วไป
เกี่ยวกับประเภทที่ได้มาและวิธีการทั่วไปในเวอร์ชันใหม่เอกสารยังให้ตัวอย่างที่ซับซ้อนกว่าเล็กน้อย ฉันโพสต์ไว้ที่นี่ หลักการเหมือนกับตัวอย่าง Serializable ด้านบนดังนั้นฉันจะไม่เข้าไปดูรายละเอียด หากคุณต้องการรวมมันคุณสามารถดู:
Public Class BoxDemo {สาธารณะคงที่ <u> เป็นโมฆะ addbox (u u, java.util.list <box <u>> กล่อง) {box <u> box = กล่องใหม่ <> (); box.set (u); boxes.add (กล่อง); } สาธารณะคงที่ <u> โมฆะเอาท์พุทบ็อกซ์ (java.util.list <box <u>> กล่อง) {int counter = 0; สำหรับ (กล่อง <u> กล่อง: กล่อง) {u boxContents = box.get (); System.out.println ("Box #" + Counter + "มี [" + BoxContents.toString () + "]"); เคาน์เตอร์ ++; }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {java.util.arraylist <box <จำนวนเต็ม >> listofintegerboxes = ใหม่ java.util.arraylist <> (); BoxDemo. <จำนวนเต็ม> addbox (Integer.Valueof (10), listofintegerboxes); boxdemo.addbox (Integer.valueof (20), listofintegerboxes); boxdemo.addbox (Integer.valueof (30), listofintegerboxes); boxdemo.outputboxes (listofintegerboxes); -เอาต์พุตรหัสด้านบนคือ:
กล่อง #0 ประกอบด้วย [10] กล่อง #1 มี [20] กล่อง #2 ประกอบด้วย [30]
ให้ฉันพูดถึงว่าจุดเน้นของวิธีการทั่วไป addBox คือคำอธิบายประเภทที่คุณไม่จำเป็นต้องแสดงในการโทรวิธีในเวอร์ชัน Java ใหม่เช่นนี้:
BoxDemo. <จำนวนเต็ม> addbox (Integer.Valueof (10), listofintegerboxes);
คอมไพเลอร์สามารถอนุมานได้โดยอัตโนมัติว่าประเภทพารามิเตอร์เป็น Integer จากพารามิเตอร์ที่ส่งผ่านไปยัง addBox
4.2 การอนุมานประเภทและตัวสร้างทั่วไปของคลาสทั่วไปและไม่ใช่แบบดั้งเดิม
อืม ... นี่อาจเป็นประโยคที่ดีกว่าในภาษาอังกฤษ: การอนุมานประเภทและตัวสร้างทั่วไปของชั้นเรียนทั่วไปและไม่ใช่ภาษาศาสตร์
ในความเป็นจริงตัวสร้างทั่วไปไม่ใช่สิทธิบัตรสำหรับชั้นเรียนทั่วไป ชั้นเรียนที่ไม่ใช่ Generic ยังสามารถมีตัวสร้างทั่วไปของตัวเอง ดูตัวอย่างนี้:
คลาส myclass <x> {<t> myclass (t t) {// ... }}หากการสร้างอินสแตนซ์ต่อไปนี้ถูกสร้างขึ้นในคลาส MyClass:
ใหม่ myclass <integer> ("") ตกลงที่นี่เราแสดงให้เห็นว่าพารามิเตอร์ Type X ของ MyClass เป็น Integer และสำหรับตัวสร้างคอมไพเลอร์จะหักค่าใช้จ่ายว่าพารามิเตอร์ที่เป็นทางการ T เป็น String ตามวัตถุ String ที่เข้ามา ("") สิ่งนี้ถูกนำไปใช้ในเวอร์ชัน Java7 มีการปรับปรุงอะไรบ้างใน Java8? หลังจาก Java8 เราสามารถเขียนอินสแตนซ์ของคลาสทั่วไปที่มีตัวสร้างทั่วไปเช่นนี้:
MyClass <Integer> myObject = ใหม่ myClass <> (""); ใช่มันยังคงเป็นคู่ของวงเล็บมุม (<>) ซึ่งเรียกว่าไดมอนด์เพื่อให้คอมไพเลอร์ของเราสามารถอนุมานได้โดยอัตโนมัติว่าพารามิเตอร์ที่เป็นทางการ x เป็น Integer และ T คือ String นี่คือสิ่งที่คล้ายกับตัวอย่างเริ่มต้นของเราของ Map<String,String> ยกเว้นว่ามีการสร้างโครงสร้างทั่วไป
ควรสังเกตว่าประเภทที่ได้มาจากประเภทพารามิเตอร์ของการโทรเท่านั้นประเภทเป้าหมาย (จะกล่าวถึงในไม่ช้า) และประเภทการส่งคืน (หากมีการส่งคืน) และไม่สามารถได้รับตามข้อกำหนดบางอย่างหลังจากโปรแกรม
4.3 ประเภทเป้าหมาย
ดังที่ได้กล่าวไว้ก่อนหน้านี้คอมไพเลอร์สามารถดำเนินการตามประเภทตามประเภทเป้าหมาย ประเภทเป้าหมายของนิพจน์หมายถึงประเภทข้อมูลที่ถูกต้องที่คอมไพเลอร์ต้องการตามที่นิพจน์ปรากฏขึ้น ตัวอย่างเช่นตัวอย่างนี้:
Static <t> รายการ <t> emptylist (); รายการ <string> listone = collections.empylist ();
ที่นี่รายการ <String> เป็นประเภทเป้าหมายเนื่องจากสิ่งที่จำเป็นที่นี่คือ List<String> และ Collections.emptyList() List<T> ดังนั้นคอมไพเลอร์ที่นี่จะติดอยู่ที่ t ต้องเป็น String นี่ก็โอเคใน Java 7 และ 8 อย่างไรก็ตามใน Java 7 มันไม่สามารถรวบรวมได้ตามปกติในสถานการณ์ต่อไปนี้:
เป็นโมฆะ processStringList (รายการ <String> StringList) {// process StringList} processStringList (collections.empylist ());ในเวลานี้ Java7 จะให้ข้อความแสดงข้อผิดพลาดนี้:
// list <jobch> ไม่สามารถแปลงเป็นรายการ <String>
เหตุผล: Collections.emptyList() List<T> และ t ที่นี่ต้องใช้ประเภทเฉพาะ แต่เนื่องจากไม่สามารถอนุมานได้จากการประกาศวิธีการว่าสิ่งที่จำเป็นคือ String ไพเลอร์ให้ค่า Object t เห็นได้ชัดว่า List<Object> ไม่สามารถแปลงเป็น List<String>. ดังนั้นในเวอร์ชัน Java7 คุณต้องเรียกวิธีนี้เช่นนี้:
processStringList (คอลเลกชัน <String> emgTylist ());
อย่างไรก็ตามใน Java 8 เนื่องจากการเปิดตัวแนวคิดประเภทเป้าหมายเป็นที่ชัดเจนว่าสิ่งที่คอมไพเลอร์ต้องการคือ List<String> (นั่นคือประเภทเป้าหมายที่นี่) ดังนั้นคอมไพเลอร์จะทำให้ T ใน List<T> ต้องเป็น String ดังนั้นคำอธิบายของ processStringList(Collections.emptyList()); โอเค
การใช้ประเภทเป้าหมายนั้นชัดเจนที่สุดในการแสดงออกของแลมบ์ดา
สรุป
ตกลงข้างต้นเป็นข้อมูลเชิงลึกส่วนบุคคลเกี่ยวกับประเภทที่ได้มาในชวา โดยสรุปแล้วการหาที่สมบูรณ์แบบที่สมบูรณ์แบบมากขึ้นคือการทำให้งานแปลงบางประเภทเสร็จสมบูรณ์ซึ่งดูเหมือนจะเป็นธรรมชาติ แต่งานเหล่านี้ทั้งหมดถูกทิ้งไว้ที่คอมไพเลอร์เพื่อรับมาโดยอัตโนมัติแทนที่จะอนุญาตให้นักพัฒนาแสดง ฉันหวังว่าเนื้อหาของบทความนี้จะเป็นประโยชน์กับทุกคนในการเรียนรู้ Java หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร