เหตุใดจึงมีวิธีการเริ่มต้น?
Java 8 กำลังจะมาถึง ดังที่ได้กล่าวไว้ก่อนหน้านี้เราได้พูดคุยกันมากมายเกี่ยวกับชุดรูปแบบนี้มาก่อน แต่การแสดงออกของแลมบ์ดาสไม่ได้เป็นการเปลี่ยนแปลงกฎเกมใน Java 8 เท่านั้น
สมมติว่า Java 8 ได้รับการปล่อยตัวและมีแลมบ์ดา ตอนนี้คุณวางแผนที่จะใช้แลมบ์ดา
รายการ <?> list = ... list.faceach (…);
คำจำกัดความของ foreach ไม่สามารถพบได้ใน java.util.list หรือ java.util.collection โดยปกติแล้วจะเป็นทางออกในการเพิ่มวิธีการใหม่และการใช้งานกับอินเทอร์เฟซที่เกี่ยวข้องใน JDK อย่างไรก็ตามสำหรับการเปิดตัวของการเปิดตัวมันเป็นไปไม่ได้ที่จะเพิ่มวิธีการใหม่ให้กับอินเทอร์เฟซในขณะที่ไม่ส่งผลกระทบต่อการใช้งานที่มีอยู่
ดังนั้นหากใช้แลมบ์ดาใน Java 8 มันเป็นไปไม่ได้ที่จะใช้สำหรับห้องสมุดคอลเลกชันเนื่องจากความเข้ากันได้ไปข้างหน้า
ด้วยเหตุผลข้างต้นมีการแนะนำแนวคิดใหม่ วิธีการขยายเสมือนซึ่งโดยปกติจะเป็นวิธีการป้องกันสามารถเพิ่มลงในอินเทอร์เฟซเพื่อให้การใช้งานเริ่มต้นของพฤติกรรมของคำสั่งสามารถให้ได้
พูดง่ายๆคืออินเทอร์เฟซ Java สามารถใช้วิธีนี้ได้ ข้อได้เปรียบของวิธีการเริ่มต้นคือสามารถเพิ่มวิธีการเริ่มต้นใหม่ให้กับอินเทอร์เฟซโดยไม่ทำลายการใช้งานอินเตอร์เฟส
ในความคิดของฉันนี่ไม่ใช่ลักษณะของ Java ที่จะใช้ทุกวัน แต่สามารถใช้แลมบ์ดาเพื่อใช้แลมบ์ดาตามธรรมชาติได้อย่างแน่นอน
ตัวอย่างที่ง่ายที่สุด
มาดูตัวอย่างที่ง่ายที่สุด: อินเทอร์เฟซ A, คลาส Clazz ใช้อินเทอร์เฟซ A.
อินเทอร์เฟซสาธารณะ A {void foo () {system.out.println ("การโทร A.foo ()");} คลาสคลาสสาธารณะคลาสคลาส A {}สามารถรวบรวมรหัสได้แม้ว่าคลาส CLAZZ จะไม่ใช้วิธี FOO () การใช้งานเริ่มต้นของวิธี Foo () มีให้ในอินเตอร์เฟส A.
รหัสไคลเอนต์โดยใช้ตัวอย่างนี้:
clazz clazz = new clazz ();
การสืบทอดหลายครั้ง?
มีคำถามทั่วไป: ผู้คนจะถามเมื่อพวกเขาได้ยินคุณลักษณะใหม่ของวิธีการเริ่มต้น "" หากคลาสใช้สองอินเทอร์เฟซและอินเตอร์เฟสทั้งสองจะกำหนดวิธีการเริ่มต้นด้วยลายเซ็นเดียวกัน กับตัวอย่างก่อนหน้า:
อินเทอร์เฟซสาธารณะ A {void foo () {system.out.println ("การโทร A.foo ()");รหัสนี้ไม่สามารถรวบรวมเหตุผลต่อไปนี้:
Java: Class Clazz จาก Type A ถึง B ถึง Foo () ได้รับค่าเริ่มต้นที่ไม่เกี่ยวข้อง
เพื่อที่จะซ่อมแซมสิ่งนี้ใน CLAZZ เราต้องแก้วิธีการเขียนความขัดแย้งใหม่ด้วยตนเอง:
คลาสสาธารณะ Clazz ใช้ A, B {Public Void Foo () {}}} แต่เราควรทำอย่างไรถ้าเราต้องการเรียกใช้วิธีการใช้งานเริ่มต้นจากอินเทอร์เฟซ A แทนที่จะใช้วิธีการของเราเอง สิ่งนี้เป็นไปได้อ้าง foo () ใน a ดังที่แสดงด้านล่าง:
คลาสสาธารณะ Clazz ใช้ A, B {Public Void Foo () {A.super.foo ();}}} ตอนนี้ฉันไม่สามารถมั่นใจได้ว่าฉันชอบวิธีแก้ปัญหาสุดท้ายนี้ บางทีมันอาจจะกระชับกว่าการรับรู้วิธีเริ่มต้นในลายเซ็นตามที่ประกาศไว้ในร่างแรกของข้อมูลจำเพาะวิธีการเริ่มต้น:
Class Class Public ใช้ A, B {Public Void Foo () ค่าเริ่มต้น A.foo;}แต่สิ่งนี้เปลี่ยนไวยากรณ์ใช่ไหม? หากอินเทอร์เฟซ A และอินเตอร์เฟส B กำหนดวิธีการเริ่มต้นจำนวนมากที่ขัดแย้งกันและฉันยินดีที่จะใช้วิธีการเริ่มต้นของอินเทอร์เฟซทั้งหมด A เพื่อแก้ปัญหาความขัดแย้ง? ในปัจจุบันฉันต้องแก้ไขความขัดแย้งหลังจากนั้นอีกครั้งเพื่อเขียนความขัดแย้งทุกคู่ สิ่งนี้อาจต้องใช้งานจำนวนมากและเขียนรหัสเทมเพลตจำนวนมาก
ฉันประเมินว่าวิธีการแก้ไขความขัดแย้งนั้นต้องการการอภิปรายมากมาย แต่ดูเหมือนว่าผู้สร้างตัดสินใจที่จะยอมรับภัยพิบัติที่หลีกเลี่ยงไม่ได้
ตัวอย่างจริง
ตัวอย่างจริงของวิธีการเริ่มต้นสามารถพบได้ในกระเป๋าต้นของ JDK8 กลับไปที่ตัวอย่างของวิธีการรวบรวมของคอลเลกชันเราสามารถค้นหาได้ในอินเตอร์เฟส java.lang.iterable การใช้งานเริ่มต้นของมันมีดังนี้:
@FunctionalInterfacepublic อินเตอร์เฟส iterable <t> {iterator <t> iterator (); Foreach ใช้พารามิเตอร์ของ java.util.function.consumer ประเภทฟังก์ชันประเภทอินเตอร์เฟสซึ่งช่วยให้เราสามารถผ่านนิพจน์แลมบ์ดาหรือการอ้างอิงวิธีการดังนี้:
รายการ <?> list = ... list.faceach (system.out :: println);
วิธีการโทร
ลองมาดูกันว่าวิธีการเริ่มต้นที่เรียกจริง หากคุณไม่คุ้นเคยกับปัญหานี้คุณอาจสนใจอ่าน Labs Rebel เกี่ยวกับ Java Bytes
จากมุมมองของรหัสลูกค้าวิธีเริ่มต้นเป็นเพียงวิธีเสมือนจริงทั่วไป ดังนั้นชื่อควรเป็นวิธีการขยายเสมือนจริง ดังนั้นสำหรับตัวอย่างง่ายๆของวิธีการเริ่มต้นเป็นอินเทอร์เฟซรหัสไคลเอนต์จะเรียกอินเทอร์เฟซโดยอัตโนมัติในสถานที่ที่มีการเรียกวิธีการเริ่มต้น
clazz = new clazz ();
หากความขัดแย้งของวิธีการเริ่มต้นได้รับการแก้ไขแล้วเมื่อเราแก้ไขวิธีการเริ่มต้นและระบุหนึ่งในอินเทอร์เฟซ InfoKespecial จะระบุการใช้งานของการใช้งานอินเตอร์เฟสของการโทรที่เฉพาะเจาะจง
Class Public ใช้ A, B {Public Void Foo () {A.super.foo (); ต่อไปนี้เป็นผลลัพธ์ของ Javap:
โมฆะสาธารณะ foo (); รหัส: 0: aload_01: invokespecial #2 // interfacemethod a.foo: / () v4: return: return
อย่างที่คุณเห็น: คำแนะนำของ Invokespecial ใช้เพื่อเรียกใช้วิธีการอินเตอร์เฟส foo () จากมุมมองของ bytecode นี่ยังคงเป็นสิ่งใหม่เพราะก่อนที่คุณจะสามารถเรียกวิธีการนี้ได้โดยชี้ไปที่คลาส (ผู้ปกครอง) แทนที่จะเป็น Super ที่ชี้ไปที่อินเทอร์เฟซ
ในที่สุด ...
วิธีการเริ่มต้นคือภาษาเสริมที่น่าสนใจสำหรับภาษา Java เป้าหมายหลักของนิพจน์เริ่มต้นคือการพัฒนาอินเทอร์เฟซ JDK มาตรฐานและในที่สุดเมื่อเราเริ่มใช้การแสดงออกของแลมบ์ดาสของ Java 8 เราให้ประสบการณ์การเปลี่ยนแปลงที่ราบรื่นแก่เรา ใครจะรู้บางทีเราอาจจะเห็นวิธีการเริ่มต้นในการออกแบบ API ในอนาคต