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 表达式中需要实现的代码;然后在运行时创建一个实现了接口的内部类,在接口方法中调用编译器生成好的私有静态方法。

相关文章
|
21天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
38 1
|
4天前
|
Java
探索Java中的Lambda表达式
【10月更文挑战第37天】本文将带你深入理解Java的Lambda表达式,从基础语法到高级特性,通过实例讲解其在函数式编程中的应用。我们还将探讨Lambda表达式如何简化代码、提高开发效率,并讨论其在实际项目中的应用。
|
6天前
|
Java API
Java中的Lambda表达式与函数式编程####
【10月更文挑战第29天】 本文将深入探讨Java中Lambda表达式的实现及其在函数式编程中的应用。通过对比传统方法,我们将揭示Lambda如何简化代码、提高可读性和维护性。文章还将展示一些实际案例,帮助读者更好地理解和应用Lambda表达式。 ####
|
11天前
|
Java API 开发者
Java中的Lambda表达式与函数式编程####
在Java的演变过程中,Lambda表达式和函数式编程的引入无疑是一次重大的飞跃。本文将深入探讨Lambda表达式的定义、用法及优势,并结合实例说明如何在Java中利用Lambda表达式进行函数式编程。通过对比传统编程方式,揭示Lambda表达式如何简化代码、提高开发效率和可维护性。 ####
|
12天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
12天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
12天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
|
17天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
20天前
|
Java API
[Java]Lambda表达式
本文主要介绍了Java中的Lambda表达式,包括其优化匿名内部类的方式、使用规范、内置函数式接口及方法引用等内容。文章详细解析了Lambda的基础语法、参数列表、方法体的简化规则,以及如何利用Lambda优化代码。此外,还探讨了Lambda的作用域和引用规则,强调了对局部变量、成员变量和常量的访问限制,旨在帮助读者全面理解和掌握Lambda表达式的应用。
13 0
[Java]Lambda表达式
|
21天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
本系列教程详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。对于希望快速学习Kotlin的用户,推荐查看“简洁”系列教程。本文档重点介绍了Kotlin与Java混编的技巧,包括代码转换、类调用、ProGuard问题、Android library开发建议以及在Kotlin和Java之间互相调用的方法。
18 1