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

相关文章
|
6天前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
9 0
|
6天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
13 0
|
1月前
|
存储 安全 Java
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
线程使程序能够通过同时执行多个任务而更有效地运行。 线程可用于在不中断主程序的情况下在后台执行复杂的任务。 创建线程 有两种创建线程的方式。 扩展Thread类 可以通过扩展Thread类并覆盖其run()方法来创建线程:
100 1
深入理解 Java 多线程、Lambda 表达式及线程安全最佳实践
|
1月前
|
算法 搜索推荐 Java
数据结构与算法(Java篇)笔记--希尔排序
数据结构与算法(Java篇)笔记--希尔排序
|
6天前
|
Java API
Java基础—笔记—内部类、枚举、泛型篇
本文介绍了Java编程中的内部类、枚举和泛型概念。匿名内部类用于简化类的创建,常作为方法参数,其原理是生成一个隐含的子类。枚举用于表示有限的固定数量的值,常用于系统配置或switch语句中。泛型则用来在编译时增强类型安全性,接收特定数据类型,包括泛型类、泛型接口和泛型方法。
9 0
|
6天前
|
Java 开发者
Java中的Lambda表达式:简洁、灵活的编程利器
在现代软件开发中,编写简洁、高效的代码是至关重要的。Java中的Lambda表达式为开发者提供了一种简洁、灵活的编程方式,使得代码更具可读性和可维护性。本文将探讨Lambda表达式的基本概念、语法结构以及在实际项目中的应用,以帮助读者更好地理解和运用这一强大的编程工具。
5 0
|
8天前
|
存储 Java API
java8新特性 lambda表达式、Stream、Optional
java8新特性 lambda表达式、Stream、Optional
|
22天前
|
Java API 开发者
Java中的Lambda表达式及其应用
本文将介绍Java中的Lambda表达式,探讨其在函数式编程中的作用和应用。通过对Lambda表达式的语法、特点以及实际应用场景的详细分析,读者将能够更好地理解并运用Lambda表达式,从而提高代码的简洁性和可读性。
16 1
|
24天前
|
分布式计算 Java 程序员
Java 8新特性之Lambda表达式与Stream API
本文将详细介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种简洁、匿名的函数表示方法,它允许我们将函数作为参数传递给其他方法。而Stream API则是一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而提高代码的可读性和可维护性。通过本文的学习,你将能够掌握Lambda表达式和Stream API的基本用法,以及如何在项目中应用这两个新特性。
28 10
|
24天前
|
Java API 数据处理
Java 8新特性之Lambda表达式与Stream API
本文将介绍Java 8中的两个重要特性:Lambda表达式和Stream API。Lambda表达式是一种新的语法结构,允许我们将函数作为参数传递给方法。而Stream API则是一种处理数据的新方式,它允许我们对数据进行更简洁、更高效的操作。通过学习这两个特性,我们可以编写出更简洁、更易读的Java代码。