Java InvokeDynamic (part 4 Lambda expression)
Tags: java, lambda
Published: 2020-04-03 15:27:04

---
title:  Java InvokeDynamic (part 4 Lambda expression)
published: true
description: 
tags: java,lambda
---

>ตอนนี้ใช้พื้นฐานจาก part ก่อนหน้านี้นะครับ
[อ่าน part 3 invokedynamic bytecode จากที่นี่](https://dev.to/pramoth/java-invokedynamic-part-3-bytecode-36jk)


ในตอนนี้นี้จะอธิบายในส่วนของ lambda express ซึ่งมีในจาวา 8 ขึ้นไป โดยตัว lambda expression นี้จะอิมพรีเมนต์แตกต่างกับ annonymouse innter class ออกไป

lambda expression ในจาวา 8 ส่วนหนึ่งจะใช้ฟีเจอร์ที่มีเข้ามาตั้งแต่ในจาวา 7 นั่นก็คือ invokedynamic ซึ่งถูกออกแบบมาให้รองรับกับพวกภาษาไดนามิคที่รันบน JVM อย่างพวก JRuby,Groovy เพื่อให้ resolve target method ที่จะถูกเรียกได้ตอนรันไทม์ (เดิมทีถ้าเราจะทำแบบนี้จะต้องใช้ Reflection)

ผมจะขอยกตัวอย่างจากโค๊ดให้ดูเลยนะครับ ให้โฟกัสที่ `test()` นะครับ
```java
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` ที่ได้จะออกมาเป็น 
```java
private static void lambda$test$0();
```

- กรณีที่ lambda expression อ้างถึงตัวแปรใน outer scope (ตัวอย่างการเรียกใช้งานในเมธอด `mimic_call_lambda$test$1(String captureVariable)`)
  `target method` ที่ได้จะออกมาเป็น 
```java
 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
[อ่านได้จากที่นี่](https://medium.com/@pramothsuwanpech/%E0%B8%88%E0%B8%B2%E0%B8%A7%E0%B8%B2%E0%B8%84%E0%B8%AD%E0%B8%A1%E0%B9%84%E0%B8%9E%E0%B8%A5%E0%B9%8C%E0%B9%80%E0%B8%A5%E0%B8%AD%E0%B8%A3%E0%B9%8C%E0%B8%AD%E0%B8%B4%E0%B8%A1%E0%B8%9E%E0%B8%A3%E0%B8%B5%E0%B9%80%E0%B8%A1%E0%B8%99%E0%B8%95%E0%B9%8C-closure-%E0%B8%AD%E0%B8%A2%E0%B9%88%E0%B8%B2%E0%B8%87%E0%B9%84%E0%B8%A3-f1b98d951de4)


[source code](https://github.com/pramoth/invokedynamic)