Java Lambda 的秘密 | Java Debug 笔记

简介: Java Lambda 的秘密 | Java Debug 笔记

新发布的 Java 8 包含众多新特性,其中之一是 Java 8 支持了被 .net 玩烂了的 Lambda 表达式。


当然我们也不能说 Java 是后知后觉,Java 有自身的考虑。我觉得可能性更大的是 Java 支持 Lambda,只是采用了这种语法(或者说这种编程方式),实现上仍然是以前那套东西,只是在编译阶段和运行阶段做了一些“手脚”,让大家吃上了 Lambda 这颗语法糖。


基本使用



先看看 Lambda 表达式最简单的使用:


public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("baidu.com")).start();
    }
}
复制代码


经常用 Thread() 写 Demo 的同学,肯定能一眼发现这当中的  Lambda 表达式其实就是对应了 Runnable 中的 run() 方法。但是第一次接触 Lambda 表达式的同学,可能很难理解,这怎么就能对应上 Runnable 中的 run()  方法了?其实这里能用  Lambda 表达式是因为 Runnable 接口属于函数式接口。


所谓的函数式接口是指该接口只有唯一一个需要被实现的抽象接口。


这里的 Thread(Runnable target) 所需要传入的 Runnable 对象,正是一个只有一个方法需要实现的接口,属于函数式接口。而且函数式接口还有一个显式的特征,就是会带一个 @FunctionalInterface 注解:


@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
复制代码


除了无参、单行代码的情况,Lambda 表达式支持以下几种写法:


  • 执行语句块
    public class Main { public static void main(String[] args) { new Thread(() -> { System.out.println("baidu.com"); System.out.println("baidu.com"); }).start(); } }
  • 接受显式参数
    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (Integer a, Integer b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); } }
  • 接受隐式参数
    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (a, b) -> { System.out.println(a); System.out.println(b); return Integer.compare(a, b); }); } }
  • 自动处理返回值
    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, (a, b) -> Integer.compare(a, b)); } }
  • 方法引用
    public class Main { public static void main(String[] args) { List list = new LinkedList<>(); // To prepare data ... Collections.sort(list, Integer::compare); } }


也就是说,最简单的 Lambda 表达式的形式化格式为:parameters -> an expression ,当有多个语句要执行时,可以使用 parameters -> {expressions;};。同时 Lambda 表达式具有推导参数类型和自动处理返回值的能力。


Lambda 的实现细节



在 Java 8 之前,我们主要有三种方式达到类似 Lambda 表达式的效果


  1. 定义类来实现接口:
    public class Main { public static void main(String[] args) { Runnable runnable = new RunnableOuterImpl(); new Thread(runnable).start(); } }
  2. 使用内部类:
    public class Main { static final class RunnableImpl implements Runnable { @Override public void run() { System.out.println("baidu.com"); } }


public static void main(String[] args) {
     Runnable runnable = new RunnableImpl();
     new Thread(runnable).start();
 }
复制代码


  1. }


  1. 使用匿名内部类:


public class Main { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("baidu.com"); } }).start(); } }


那么 Lambda 是通过以上三种方式的何种来实现的呢?


事实上,Lambda 表达式是通过内部类来实现的。完整的 Lambda 表达式的流程如下:


  1. 在编译期,先创建一个私有的静态方法,静态方法的实现就是我们 Lambda 表达式里要做的事情


这一点,我们可以通过编译源码,再重新编译回去证实。我们可以编译以上的 Lambda 版本的 java 文件,得到对应的 class 文件,再通过 javap 进行反编译。得到以下反编译内容:


Compiled from "Main.java"
public class com.baidu.Main {
  public com.baidu.Main();
  public static void main(java.lang.String[]);
  private static void lambda$main$0();
}
复制代码


可以看到在 Main 类里面多了一个 lambdamainmainmain0 方法,说明 Lambda 表达式首先先生成一个私有的静态函数。这个函数里的实现就是我们在 Lambda 表达式里面的执行内容。


  1. 在运行时,Lambda 表达式首次被执行时,动态生成一个对应接口的实现类


由于是运行时,最好的验证方法是通过断点的形式。在执行 Lambda 表达式的位置断点,可以看到执行表达式的时,是调用了 java.lang.invoke.LambdaMetafactory 类里面的 metafactory() 方法


public static CallSite metafactory(MethodHandles.Lookup caller,
                                   String invokedName,
                                   MethodType invokedType,
                                   MethodType samMethodType,
                                   MethodHandle implMethod,
                                   MethodType instantiatedMethodType)
    throws LambdaConversionException {
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                         invokedName, samMethodType,
                                         implMethod, instantiatedMethodType,
                                         false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
}
复制代码


为了确定这个动态生成的类在运行时真实存在,我们可以执行 Main.java 的时候,指定 JVM 参数 -Djdk.internal.lambda.dumpProxyClasses 类导出 Lambda 表达式生成的动态代理类。之后我们会在 Main.class 的同级目录下看到一个新的 class 文件:Main$$Lambda$1.class。


然后通过通过 javap 反编译一下:


➜  WorkSpace javap -c -p com.baidu.Main\$\$Lambda\$1
final class com.baidu.Main$$Lambda$1 implements java.lang.Runnable {
  private com.baidu.Main$$Lambda$1();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: return
  public void run();
    Code:
       0: invokestatic  #17                 // Method com/baidu/Main.lambda$main$0:()V
       3: return
}
复制代码


可以看到运行时,JVM 会为 Lambda 表达式创建一个 Runnable 接口的实现类。里面有一个默认的构造方法和必须实现的 run() 方法。在 run() 方法中,调用了一个静态方法,这个静态方法就是我们刚刚说的,在编译期,生成的那个私有静态方法 lambdamainmainmain0()。


总结



总结一下,Lambda 表达式的实现本质就是内部类。先在编译器创建一个私有的静态方法,装着我们在 Lambda 表达式中需要实现的代码;然后在运行时创建一个实现了接口的内部类,在接口方法中调用编译器生成好的私有静态方法。

相关文章
|
2月前
|
安全 Java API
Java中的Lambda表达式:简洁与功能的结合
Java中的Lambda表达式:简洁与功能的结合
359 211
|
4月前
|
SQL JSON 安全
Java 8 + 中 Lambda 表达式与 Stream API 的应用解析
摘要:本文介绍了Java 8+核心新特性,包括Lambda表达式与Stream API的集合操作(如过滤统计)、函数式接口的自定义实现、Optional类的空值安全处理、接口默认方法与静态方法的扩展能力,以及Java 9模块化系统的组件管理。每个特性均配有典型应用场景和代码示例,如使用Stream统计字符串长度、Optional处理Map取值、模块化项目的依赖声明等,帮助开发者掌握现代Java的高效编程范式。(150字)
79 1
|
6月前
|
Java 编译器 API
Java Lambda 表达式:以 Foo 接口为例深入解析
本文深入解析了 Java 8 中 Lambda 表达式的用法及其背后的函数式接口原理,以 `Foo` 接口为例,展示了如何通过简洁的 Lambda 表达式替代传统匿名类实现。文章从 Lambda 基本语法、函数式接口定义到实际应用层层递进,并探讨默认方法与静态方法的扩展性,最后总结常见误区与关键点,助你高效优化代码!
130 0
|
7月前
|
SQL Rust Java
怎么理解Java中的lambda表达式
Lambda表达式是JDK8引入的新语法,用于简化匿名内部类的代码写法。其格式为`(参数列表) -&gt; { 方法体 }`,适用于函数式接口(仅含一个抽象方法的接口)。通过Lambda表达式,代码更简洁灵活,提升Java的表达能力。
125 4
|
9月前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
418 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
9月前
|
前端开发 JavaScript Java
Java构建工具-maven的复习笔记【适用于复习】
这篇文档由「潜意识Java」创作,主要介绍Maven的相关知识。内容涵盖Maven的基本概念、作用、项目导入步骤、依赖管理(包括依赖配置、代码示例、总结)、依赖传递、依赖范围以及依赖的生命周期等七个方面。作者擅长前端开发,秉持“得之坦然,失之淡然”的座右铭。期待您的点赞、关注和收藏,这将是作者持续创作的动力! [个人主页](https://blog.csdn.net/weixin_73355603?spm=1000.2115.3001.5343)
119 3
|
10月前
|
Java 数据库连接 编译器
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
217 0
|
10月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)

热门文章

最新文章