ตอนนี้ใช้พื้นฐานจาก part ก่อนหน้านี้นะครับ
อ่าน part 3 invokedynamic bytecode จากที่นี่
ในตอนนี้นี้จะอธิบายในส่วนของ lambda express ซึ่งมีในจาวา 8 ขึ้นไป โดยตัว lambda expression นี้จะอิมพรีเมนต์แตกต่างกับ annonymouse innter class ออกไป
lambda expression ในจาวา 8 ส่วนหนึ่งจะใช้ฟีเจอร์ที่มีเข้ามาตั้งแต่ในจาวา 7 นั่นก็คือ invokedynamic ซึ่งถูกออกแบบมาให้รองรับกับพวกภาษาไดนามิคที่รันบน JVM อย่างพวก JRuby,Groovy เพื่อให้ resolve target method ที่จะถูกเรียกได้ตอนรันไทม์ (เดิมทีถ้าเราจะทำแบบนี้จะต้องใช้ Reflection)
ผมจะขอยกตัวอย่างจากโค๊ดให้ดูเลยนะครับ ให้โฟกัสที่ test()
นะครับ
public class LambdaTest {
public void test() {
// 1. Not refer to outer scope
// javac will create a synthetic static method name "lambda$test$0()"
Runnable a = () -> System.out.println("Hello From Lambda. no capture variable");
// 2. Refer/capture value from outer scope
// javac will create a synthetic static method name "lambda$test$1(java.lang.String)V"
String name = "Pramoth";
Runnable b = () -> System.out.println("Hello From Lambda " + name + " with capture variable");
}
public static void main(String[] args) throws Throwable {
// จำลอง invokedynamic ไปที่ bootstrap `lambda$test$0`
Runnable r = mimic_call_lambda$test$0();
// print "Hello From Lambda. no capture variable"
r.run();
// จำลอง invokedynamic ไปที่ bootstrap `lambda$test$1`
r = mimic_call_lambda$test$1("Pramoth");
// print "Hello From Lambda Pramoth with capture variable"
r.run();
}
private static Runnable mimic_call_lambda$test$0() throws Throwable {
// LambdaMetafactory จะทำการ implement Runnable.run ด้วยการทำ bytecode manipulation โดย ASM ดูการทำงานใน java.lang.invoke.InnerClassLambdaMetafactory
CallSite lambda$test$CallSite = LambdaMetafactory.metafactory(MethodHandles.lookup(),
"run", // method ที่จะ implement คือ Runnable.run()
MethodType.methodType(Runnable.class), // method type ของ factory method จะรีเทริน Runnable อารมณ์คล้ายๆ Runnable Factory.create()
MethodType.methodType(void.class), // อิมพลีเมนต์เมธอดจะรีเทร์น void `public void run()`
MethodHandles.lookup().findStatic(LambdaTest.class, "lambda$test$0", MethodType.methodType(void.class)), // MethodHandle ที่เอาไว้เรียกเมธอดที่ javac สร้างมาเป็น body ของ lambda
MethodType.methodType(void.class)); // อิมพลีเมนต์เมธอดจะรีเทร์น void `public void run()` อันนี้ที่เพราะสามารถรีเทริน more specific class ได้ กรณีนี้ ไม่ได้ใช้ก็ระบุ void.class เหมือนรีเทรินของอิมพลีเมนต์เมธอด
// invoke โดยไม่มี capture vairable
return (Runnable) lambda$test$CallSite.dynamicInvoker().invokeExact();
}
private static Runnable mimic_call_lambda$test$1(String captureVariable) throws Throwable {
CallSite lambda$test$CallSite = LambdaMetafactory.metafactory(MethodHandles.lookup(),
"run",
MethodType.methodType(Runnable.class, String.class), // method type ของ factory method จะรีเทริน Runnable แต่ว่าเคสนี้มีการ capture variable ด้วย ดังนั้นจึงต้องระบุ parameter ด้วย อารมณ์คล้ายๆ Runnable Factory.create(String)
MethodType.methodType(void.class),
MethodHandles.lookup().findStatic(LambdaTest.class, "lambda$test$1", MethodType.methodType(void.class, String.class)),
MethodType.methodType(void.class));
// invoke พร้อมกับ capture vairable
return (Runnable) lambda$test$CallSite.dynamicInvoker().invokeExact(captureVariable);
}
}
javac จะเปลี่ยน body ของ lambda expression ไปเป็น target method
ที่มีชื่อ
- private static R
lambda$(method)$x
ในกรณีอยู่ใน method - private static R
lambda$new$x
ในกรณีอยู่ใน instance initializer block หรือตรง initial ค่าของ instance variable - private static R
lambda$static$x
ในกรณีอยู่ใน static initializer block หรือตรง initial ค่าของ class variable โดย x จะเป็น 0 1 2 .... และ R คือ return type
เมธอดที่สร้างขึ้นจะมีแอททริบิว ACC_SYNTHETIC ซึ่งหมายถึง compiler เป็นผู้สร้างให้
จากนั้น javac จะสร้าง CONST_MethodHandle_info ใน bytecode เพื่อให้ invokedynamic เรียกใช้งาน โดยในภาษาจะมี api ที่เป็น generic bootstrap method
ที่ใช้สำหรับอิมพลีเมนต์ functional interface อยู่ ซึ่งก็คือคลาส java.lang.invoke.LambdaMetafactory
เมื่อ invokedynamic
ทำงานจะไปเรียก bootstrap method
java.lang.invoke.LambdaMetafactory.metafactory() เพื่อสร้าง CallSite ที่เมื่อเรียก invoke*() จะรีเทริน implementation ของ functional interface
นั้นๆ ซึ่งตัว implementation นี้จะอิมพลีเมนต์ functional method
ด้วยการไปเรียก MethodHandle ของ target method
อีกที (อธิบายไว้ในโคีดนะครับ)
อันนี้เป็น internal representation นะครับ ไม่ควรเอาไปเรียกใช้ตรงๆอย่างในโค๊ด อันนี้แค่เดโม
โดยผมยกตัวอย่างไว้ 2 กรณี
- กรณีที่ lambda expression ไม่ได้อ้างถึงตัวแปรใน outer scope
(ตัวอย่างการเรียกใช้งานในเมธอด
mimic_call_lambda$test$0()
)target method
ที่ได้จะออกมาเป็น
private static void lambda$test$0();
- กรณีที่ lambda expression อ้างถึงตัวแปรใน outer scope (ตัวอย่างการเรียกใช้งานในเมธอด
mimic_call_lambda$test$1(String captureVariable)
)target method
ที่ได้จะออกมาเป็น
private static void lambda$test$1(java.lang.String);
จะสังเกตว่าจะมีการรับค่าที่ capture มาจาก outer scope มาด้วย
ตัวอย่าง bytecode ที่ javac สร้างมาให้นะครับ
private static void lambda$test$1();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #26 // String Hello From Lambda. no capture variable
5: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
private static void lambda$new$0();
descriptor: ()V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #27 // String dd
5: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
}
จบแล้วครับสำหรับ invokedynamic หรือ Indy!
สำหรับใครที่อยากรู้ว่า javac อิมพรีเมนต์ annonymous inner class อย่าไร รวมถึงวิธีการที่มัน cature variable จาก outer scope
อ่านได้จากที่นี่
Top comments (0)