# Java Lambda 表达式

# 1\. 背景

Java 8 引入了 Lambda 表达式和函数式接口，其基础技术是 [`invokedynamic`](https://blogs.oracle.com/javamagazine/post/understanding-java-method-invocation-with-invokedynamic) 指令和 [`LambdaMetafactory`](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html) 动态类生成机制。相比于传统的匿名内部类和反射，它具有更好的性能和编译器可分析性。（对于编译器跟JVM虚拟机来说，对于确定性的内容更容易优化，比如内联、分支预测、逃逸分析等等）

# 2\. Lambda 是如何被实现的？

```java
int num = 10;
Runnable r = () -> System.out.println(num);//Lambda语法糖
```

Java 编译器会将 Lambda 表达式 desugar 成调用 `invokedynamic` 指令，该指令的 Bootstrap 方法通常是 `LambdaMetafactory.metafactory` 或 `altMetafactory`。

## 2.1. 编译产物：[invokedynamic](https://cr.openjdk.org/~briangoetz/lambda/lambda-translation.html)

```java
0: bipush        10
2: istore_1                    // int num = 10;
4: aload_0                     // 加载 this
5: iload_1                     // 加载局部变量 num（被捕获）,这就是为什么lambda表达式中的变量必须是final的
6: invokedynamic #0, run()Ljava/lang/Runnable;
```

## 2.2. 实现方式

这些方法会根据传入的 [`MethodHandle`](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandle.html)（即 Lambda 实现的方法）生成一个实现某个函数式接口的匿名类，并缓存在 `CallSite` 中。

```java
// 编译后 roughly 等价于：
CallSite site = LambdaMetafactory.metafactory(...);
Runnable r = (Runnable) site.getTarget().invoke();
```

# 3\. 什么是 MethodHandle？

`MethodHandle` 是 Java 层面对方法、构造器、字段的直接引用，是 `java.lang.invoke` 的一部分。

```java
MethodHandle mh = lookup.findVirtual(String.class, "length", methodType(int.class));// 这里在类加载时，调用的方法签名实际上就已经确定了，所以很容易做jvm通用的优化
 
int len = (int) mh.invokeExact("hello");
```

和反射不同，它：

* 类型安全
    
* 支持高性能调用
    
* 能与 `invokedynamic` 联动做深层优化
    

对比下反射

```java
Method m = String.class.getMethod("length");
int len = (int) m.invoke("abc"); // JIT 无法预知调用目标,只有运行时才能确定的知道需要调用的对象，很难提前优化
```

`MethodHandle` vs Reflection

| **特性** | **MethodHandle** | **Reflection** |
| --- | --- | --- |
| 可内联优化 | ✅ 是 | ❌ 否 |
| 权限检查开销 | ✅ 少 | ❌ 多（每次都要） |
| 类型安全 | ✅ 静态校验（invokeExact） | ❌ 动态检查 |
| 参数处理 | ✅ 无需数组 | ❌ 参数统一打包为 Object\[\] |
| 调用速度 | ✅ 快 | ❌ 慢 |

# 4\. JVM 是怎么优化 `invokeExact` 的？

HotSpot 中的 C2 编译器（优化级别最高的 JIT 编译器）：

* **直接识别** `invokeExact` 是 `MethodHandle` intrinsic 调用
    
* **能观察到目标方法**（例如 `String.length` 是 final 的，没有虚调用成本）
    
* **直接内联调用目标**
    
* 如果在逃逸分析后发现 `MethodHandle` 是常量，可以去掉中间对象
    

> JVM 可以把 `MethodHandle.invokeExact` 优化得跟直接写 `str.length()` 一样。Lambda 表达式的序列化风险

# 5\. Lambda 表达式的序列化风险

### **5.1.1. Lambda 表达式默认不可序列化**

只有在函数式接口扩展了 [`java.io`](http://java.io/)`.Serializable` 时，lambda 才可能被序列化。

```java
@FunctionalInterface
public interface Task extends Serializable {
    void run();
}
 
Task task = () -> System.out.println("Serializable lambda");
```

### **5.1.2.  SerializedLambda** 

lambda表达式序列化的是一个 `SerializedLambda` 实例，而非真实可执行类

```java
//这意味着 反序列化时必须能加载原实现类，找到精确的方法，甚至方法签名也不能变，否则会失败。
public interface SerializedLambda extends Serializable {
    // capture context
    String getCapturingClass();
 
    // SAM descriptor
    String getDescriptorClass();
    String getDescriptorName();
    String getDescriptorMethodType();
 
    // implementation
    int getImplReferenceKind();
    String getImplClass();
    String getImplName();
    String getImplMethodType();
 
    // dynamic args -- these will individually need to be Serializable too
    Object[] getCapturedArgs();
}
```
