ฉันคอยฟังเกี่ยวกับคุณสมบัติเจ๋ง ๆ ใหม่ ๆ ที่กำลังเพิ่มเข้าไปใน JVM และหนึ่งในฟีเจอร์เจ๋ง ๆ เหล่านั้นก็คือการเรียกใช้ ฉันต้องการจะรู้ว่ามันคืออะไรและมันทำให้การเขียนโปรแกรมไตร่ตรองใน Java ง่ายขึ้นหรือดีขึ้นอย่างไร
ฉันคอยฟังเกี่ยวกับคุณสมบัติเจ๋ง ๆ ใหม่ ๆ ที่กำลังเพิ่มเข้าไปใน JVM และหนึ่งในฟีเจอร์เจ๋ง ๆ เหล่านั้นก็คือการเรียกใช้ ฉันต้องการจะรู้ว่ามันคืออะไรและมันทำให้การเขียนโปรแกรมไตร่ตรองใน Java ง่ายขึ้นหรือดีขึ้นอย่างไร
คำตอบ:
มันเป็นคำสั่ง JVM ใหม่ที่อนุญาตให้คอมไพเลอร์สร้างโค้ดที่เรียกใช้เมธอดที่มีข้อมูลจำเพาะที่หลวมกว่าที่เป็นไปได้ก่อนหน้านี้ - ถ้าคุณรู้ว่า "การพิมพ์เป็ด " คืออะไร มีไม่มากนักที่โปรแกรมเมอร์ Java สามารถทำได้ หากคุณเป็นผู้สร้างเครื่องมือคุณสามารถใช้มันเพื่อสร้างภาษาที่ยืดหยุ่นและมีประสิทธิภาพมากขึ้นโดยใช้ภาษา JVM นี่คือโพสต์บล็อกหวานจริง ๆ ที่ให้รายละเอียดมากมาย
MethodHandleซึ่งเป็นสิ่งเดียวกัน แต่มีความยืดหยุ่นมากขึ้น แต่พลังที่แท้จริงในทั้งหมดนี้ไม่ได้เพิ่มเติมในภาษาจาวา แต่ในความสามารถของ JVM เองในการสนับสนุนภาษาอื่นที่มีความไดนามิกมากขึ้น
invokedynamicซึ่งจะทำให้มัน performant (เมื่อเทียบกับห่อไว้ในที่ไม่ระบุชื่อชั้นในระดับที่เกือบจะเป็นทางเลือกเดียวก่อนที่จะแนะนำinvokedynamic) ส่วนใหญ่อาจเป็นภาษาการเขียนโปรแกรมที่ใช้งานได้จำนวนมากที่ด้านบนของ JVM จะเลือกที่จะรวบรวมสิ่งนี้แทนการเรียนแบบอินไลน์ภายใน
เมื่อเวลาผ่านไป C # ได้เพิ่มฟีเจอร์เจ๋ง ๆ , ซินแทกซ์แบบไดนามิกภายใน C #
Object obj = ...; // no static type available
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
คิดว่ามันเป็นไวยากรณ์น้ำตาลสำหรับการโทรวิธีการไตร่ตรอง มันสามารถมีแอพพลิเคชั่นที่น่าสนใจมาก ดูhttp://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
Neal Gafter ผู้รับผิดชอบประเภทไดนามิกของ C # เพิ่งเสียจาก SUN ไปยัง MS ดังนั้นจึงไม่มีเหตุผลที่จะคิดว่าสิ่งเดียวกันได้ถูกกล่าวถึงในอาทิตย์
ฉันจำได้ไม่นานหลังจากนั้นเพื่อน Java บางคนประกาศสิ่งที่คล้ายกัน
InvokeDynamic duck = obj;
duck.quack();
น่าเสียดายที่ฟีเจอร์นี้ไม่มีที่ไหนใน Java 7 ที่ผิดหวังมาก สำหรับโปรแกรมเมอร์ Java พวกเขาไม่มีวิธีที่ง่ายในการใช้ประโยชน์จากinvokedynamicในโปรแกรมของพวกเขา
invokedynamicไม่เคยตั้งใจจะใช้สำหรับโปรแกรมเมอร์ Java IMO มันไม่สอดคล้องกับปรัชญา Java เลย ถูกเพิ่มเป็นคุณลักษณะ JVM สำหรับภาษาที่ไม่ใช่ Java
มีแนวคิดสองประการที่ต้องทำความเข้าใจก่อนดำเนินการต่อไปเพื่อเรียกใช้ซินนา
1. Static กับ Dynamin Typing
คงที่ - การตรวจสอบประเภท preforms ณ เวลารวบรวม (เช่น Java)
แบบไดนามิก - การตรวจสอบประเภท preforms ที่รันไทม์ (เช่น JavaScript)
การตรวจสอบประเภทเป็นกระบวนการในการตรวจสอบว่าโปรแกรมนั้นปลอดภัยสำหรับประเภทนี้คือการตรวจสอบข้อมูลที่พิมพ์สำหรับตัวแปรคลาสและอินสแตนซ์พารามิเตอร์วิธีการค่าส่งคืนและตัวแปรอื่น ๆ เช่น Java รู้เกี่ยวกับ int, String, .. ณ เวลารวบรวมในขณะที่ชนิดของวัตถุใน JavaScript สามารถกำหนดได้ในขณะใช้งานจริงเท่านั้น
2. พิมพ์แข็งแรงและอ่อนแอ
Strong - ระบุข้อ จำกัด เกี่ยวกับประเภทของค่าที่กำหนดให้กับการทำงาน (เช่น Java)
Weak - แปลงอาร์กิวเมนต์ (casts) ของการดำเนินการหากอาร์กิวเมนต์เหล่านั้นมีประเภทที่เข้ากันไม่ได้ (เช่น Visual Basic)
รู้ว่า Java เป็นแบบคงที่และอ่อนพิมพ์คุณจะใช้ภาษาที่พิมพ์แบบไดนามิกและแข็งแกร่งใน JVM ได้อย่างไร
invokedynamic ใช้ระบบรันไทม์ที่สามารถเลือกการใช้งานที่เหมาะสมที่สุดของวิธีการหรือฟังก์ชั่น - หลังจากโปรแกรมได้รับการรวบรวม
ตัวอย่าง: การมี (a + b) และไม่รู้อะไรเกี่ยวกับตัวแปร a, b ในเวลารวบรวม, invokedynamic แม็พการดำเนินการนี้กับวิธีที่เหมาะสมที่สุดใน Java ตอนรันไทม์ เช่นถ้าปรากฎ a, b คือ Strings ให้เรียกเมธอด (String a, String b) ถ้าปรากฎว่า a, b คือ ints แล้วเรียกเมธอด (int a, int b)
invokedynamic ถูกนำมาใช้กับ Java 7
เป็นส่วนหนึ่งของJava Recordsของฉันบทความฉันฉันพูดชัดแจ้งเกี่ยวกับแรงจูงใจเบื้องหลัง Inoke Dynamic เรามาเริ่มด้วยนิยามคร่าวๆของอินดี้กันดีกว่า
Invoke Dynamic (เรียกอีกอย่างว่าIndy ) เป็นส่วนหนึ่งของJSR 292 ที่ต้องการเพิ่มประสิทธิภาพการสนับสนุน JVM สำหรับ Dynamic Type Languages หลังจากเปิดตัวครั้งแรกใน Java 7, invokedynamicopcode พร้อมกับมันjava.lang.invokeกระเป๋าเดินทางนั้นถูกใช้อย่างกว้างขวางโดยภาษาที่ใช้ JVM แบบไดนามิกเช่น JRuby
แม้ว่าอินดี้ออกแบบมาเป็นพิเศษเพื่อปรับปรุงการรองรับภาษาแบบไดนามิก แต่ก็มีให้มากกว่านั้น ตามความเป็นจริงแล้วมันเหมาะที่จะใช้ในทุกที่ที่นักออกแบบภาษาต้องการรูปแบบใด ๆ ของไดนามิกจากการแสดงกายกรรมแบบไดนามิกไปจนถึงกลยุทธ์แบบไดนามิก!
ตัวอย่างเช่นJava 8 Lambda Expressions นั้นถูกนำมาใช้จริงinvokedynamicแม้ว่า Java เป็นภาษาที่พิมพ์แบบคงที่!
สำหรับค่อนข้างบางเวลา JVM ไม่สนับสนุนสี่ประเภทวิธีการอุทธรณ์: invokestaticเรียกวิธีคงinvokeinterfaceเรียกวิธีอินเตอร์เฟซinvokespecialที่จะเรียกก่อสร้าง, super()หรือวิธีการภาคเอกชนและinvokevirtualเรียกเมธอดอินสแตนซ์
แม้จะมีความแตกต่างกัน แต่ประเภทการเรียกใช้เหล่านี้มีลักษณะทั่วไปเพียงอย่างเดียวนั่นคือเราไม่สามารถยกระดับให้กับตรรกะของเราเองได้ ในทางตรงกันข้ามinvokedynamic ช่วยให้เราสามารถ Bootstrap กระบวนการร้องขอในวิธีที่เราต้องการ จากนั้น JVM จะดูแลการโทร Bootstrapped Method โดยตรง
ครั้งแรกที่ JVM เห็นinvokedynamicการเรียนการสอนที่เรียกว่าวิธีการแบบคงที่พิเศษที่เรียกว่าวิธี Bootstrap วิธีบู๊ตสแตรปเป็นส่วนหนึ่งของรหัส Java ที่เราเขียนขึ้นเพื่อเตรียมตรรกะจริงที่จะเรียกใช้:
java.lang.invoke.CallSiteแล้ววิธีบูตส่งกลับตัวอย่างของ นี่CallSiteถือเป็นการอ้างอิงถึงวิธีการที่เกิดขึ้นจริงเช่นMethodHandleถืออ้างอิงถึงวิธีการที่เกิดขึ้นจริงคือ
นับจากนี้ไปทุกครั้งที่ JVM เห็นinvokedynamicคำสั่งนี้อีกครั้งจะข้ามเส้นทางที่ช้าและเรียกใช้ไฟล์ปฏิบัติการโดยตรง JVM จะข้ามเส้นทางช้าอย่างต่อเนื่องเว้นแต่มีการเปลี่ยนแปลงบางอย่าง
Java 14 Recordsกำลังจัดเตรียมไวยากรณ์ที่กระชับไว้เป็นอย่างดีเพื่อประกาศคลาสที่ควรจะเป็นผู้ถือข้อมูลที่โง่
พิจารณาบันทึกง่าย ๆ นี้:
public record Range(int min, int max) {}
โค้ดไบต์สำหรับตัวอย่างนี้จะเป็นดังนี้:
Compiled from "Range.java"
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
6: areturn
ในตารางวิธี Bootstrap :
BootstrapMethods:
0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 Range
#48 min;max
#50 REF_getField Range.min:I
#51 REF_getField Range.max:I
ดังนั้นวิธีการบูตสแตรปสำหรับเร็กคอร์ดจึงถูกเรียกbootstrapซึ่งอยู่ในjava.lang.runtime.ObjectMethodsคลาส อย่างที่คุณเห็นวิธีการบู๊ตสแตรปนี้ต้องการพารามิเตอร์ต่อไปนี้:
MethodHandles.Lookupแทนบริบทการค้นหา ( Ljava/lang/invoke/MethodHandles$Lookupส่วนหนึ่ง)toString, equals, hashCodeฯลฯ ) บูตจะไปเชื่อมโยง ตัวอย่างเช่นเมื่อค่าคือtoStringbootstrap จะส่งกลับ a ConstantCallSite( CallSiteที่ไม่เคยเปลี่ยนแปลง) ที่ชี้ไปที่toStringการใช้งานจริงสำหรับบันทึกนี้โดยเฉพาะTypeDescriptorสำหรับวิธีการ ( Ljava/lang/invoke/TypeDescriptor
บางส่วน)Class<?>ตัวแทนประเภทชั้นเรียนบันทึก มันเป็น
Class<Range>ในกรณีนี้min;maxกึ่งลำไส้ใหญ่แยกรายการสินค้าทั้งหมดของชื่อองค์ประกอบคือMethodHandleต่อองค์ประกอบ วิธีนี้วิธีบู๊ตสแตรปสามารถสร้างMethodHandleส่วนประกอบบนพื้นฐานสำหรับการใช้วิธีนี้โดยเฉพาะinvokedynamicการเรียนการสอนผ่านทุกข้อโต้แย้งเหล่านั้นไปยังวิธีการบูต ConstantCallSiteวิธีการบูตในทางกลับกันกลับตัวอย่างของ นี้มีการถือครองการอ้างอิงถึงการดำเนินการตามวิธีการร้องขอเช่นConstantCallSitetoString
ตรงข้ามกับ Reflection APIs java.lang.invoke API ค่อนข้างมีประสิทธิภาพเนื่องจาก JVM สามารถมองเห็นการเรียกใช้ทั้งหมดได้อย่างสมบูรณ์ ดังนั้น JVM อาจใช้การเพิ่มประสิทธิภาพทุกประเภทตราบใดที่เราหลีกเลี่ยงเส้นทางช้ามากที่สุด!
นอกเหนือจากข้อโต้แย้งด้านประสิทธิภาพแล้วinvokedynamicวิธีการนี้ยังมีความน่าเชื่อถือและเปราะน้อยกว่าเนื่องจากความเรียบง่ายความเรียบง่าย
นอกจากนี้โค้ดไบต์ที่สร้างขึ้นสำหรับ Java Records นั้นไม่ขึ้นอยู่กับจำนวนของคุณสมบัติ ดังนั้นน้อย bytecode และเวลาเริ่มต้นเร็วขึ้น
สุดท้ายสมมติว่า Java เวอร์ชันใหม่มีการใช้วิธีบูตสแตรปแบบใหม่และมีประสิทธิภาพยิ่งขึ้น ด้วยinvokedynamicแอปของเราสามารถใช้ประโยชน์จากการปรับปรุงนี้โดยไม่ต้องรวบรวมใหม่ วิธีนี้เรามีความเข้ากันได้แบบส่งต่อไบนารีบางอย่าง นั่นคือกลยุทธ์ที่เรากำลังพูดถึง!
นอกจาก Java Records แล้วInvoke dynamicยังถูกใช้เพื่อสร้างฟีเจอร์เช่น:
LambdaMetafactoryStringConcatFactory
meth.invoke(args)มันไม่ได้เป็นเรื่องแปลกที่จะเห็นภาพสะท้อนถูกใช้ในการเรียกใช้วิธีการแบบไดนามิกด้วย ดังนั้นจะinvokedynamicพอดีกับได้meth.invokeอย่างไร