ฉันคอยฟังเกี่ยวกับคุณสมบัติเจ๋ง ๆ ใหม่ ๆ ที่กำลังเพิ่มเข้าไปใน 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, invokedynamic
opcode พร้อมกับมัน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
ฯลฯ ) บูตจะไปเชื่อมโยง ตัวอย่างเช่นเมื่อค่าคือtoString
bootstrap จะส่งกลับ a ConstantCallSite
( CallSite
ที่ไม่เคยเปลี่ยนแปลง) ที่ชี้ไปที่toString
การใช้งานจริงสำหรับบันทึกนี้โดยเฉพาะTypeDescriptor
สำหรับวิธีการ ( Ljava/lang/invoke/TypeDescriptor
บางส่วน)Class<?>
ตัวแทนประเภทชั้นเรียนบันทึก มันเป็น
Class<Range>
ในกรณีนี้min;max
กึ่งลำไส้ใหญ่แยกรายการสินค้าทั้งหมดของชื่อองค์ประกอบคือMethodHandle
ต่อองค์ประกอบ วิธีนี้วิธีบู๊ตสแตรปสามารถสร้างMethodHandle
ส่วนประกอบบนพื้นฐานสำหรับการใช้วิธีนี้โดยเฉพาะinvokedynamic
การเรียนการสอนผ่านทุกข้อโต้แย้งเหล่านั้นไปยังวิธีการบูต ConstantCallSite
วิธีการบูตในทางกลับกันกลับตัวอย่างของ นี้มีการถือครองการอ้างอิงถึงการดำเนินการตามวิธีการร้องขอเช่นConstantCallSite
toString
ตรงข้ามกับ Reflection APIs java.lang.invoke
API ค่อนข้างมีประสิทธิภาพเนื่องจาก JVM สามารถมองเห็นการเรียกใช้ทั้งหมดได้อย่างสมบูรณ์ ดังนั้น JVM อาจใช้การเพิ่มประสิทธิภาพทุกประเภทตราบใดที่เราหลีกเลี่ยงเส้นทางช้ามากที่สุด!
นอกเหนือจากข้อโต้แย้งด้านประสิทธิภาพแล้วinvokedynamic
วิธีการนี้ยังมีความน่าเชื่อถือและเปราะน้อยกว่าเนื่องจากความเรียบง่ายความเรียบง่าย
นอกจากนี้โค้ดไบต์ที่สร้างขึ้นสำหรับ Java Records นั้นไม่ขึ้นอยู่กับจำนวนของคุณสมบัติ ดังนั้นน้อย bytecode และเวลาเริ่มต้นเร็วขึ้น
สุดท้ายสมมติว่า Java เวอร์ชันใหม่มีการใช้วิธีบูตสแตรปแบบใหม่และมีประสิทธิภาพยิ่งขึ้น ด้วยinvokedynamic
แอปของเราสามารถใช้ประโยชน์จากการปรับปรุงนี้โดยไม่ต้องรวบรวมใหม่ วิธีนี้เรามีความเข้ากันได้แบบส่งต่อไบนารีบางอย่าง นั่นคือกลยุทธ์ที่เรากำลังพูดถึง!
นอกจาก Java Records แล้วInvoke dynamicยังถูกใช้เพื่อสร้างฟีเจอร์เช่น:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
มันไม่ได้เป็นเรื่องแปลกที่จะเห็นภาพสะท้อนถูกใช้ในการเรียกใช้วิธีการแบบไดนามิกด้วย ดังนั้นจะinvokedynamic
พอดีกับได้meth.invoke
อย่างไร