软件测试|Lambda表达式介绍和底层实现

简介: 软件测试|Lambda表达式介绍和底层实现

如果你的需求需要匿名类来实现,例如是一个只有一个方法的接口,那么匿名类的语法可能看起来比较笨拙和不清晰,尽管匿名类比命名类更简洁,但对于只有一个方法的类来说,即使是匿名类也显得有些麻烦。还有在一些情况下,需要将功能作为参数传递给另一个方法,例如当有人单击页面上按钮时应该采取什么操作,javascript可以通过闭包实现。在java语言中,lambda表达式能够将功能视为方法参数,或将代码视为数据,而且lambda表达式可以更紧凑地表达单方法类的实例,在Swing编程和集合(Collections)编程中优势很明显。

lambda表达式

lambda表达式,也被称为闭包,它是推动 Java 8 发布的最重要新特性。lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

(parameters) -> expression或 (parameters) ->{ statements; }
AI 代码解读

可选类型声明

不需要声明参数类型,编译器可以统一识别参数值;

可选的参数圆括号

一个参数无需定义圆括号,但多个参数需要定义圆括号;

可选的大括号

如果主体包含了一个语句,就不需要使用大括号;

可选的返回关键字

如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
AI 代码解读

第一个 lambda 表达式接收 x 和 y 这两个整形参数并返回它们的和;第二个 lambda表达式不接收参数,返回整数 ‘42’;第三个 lambda 表达式接收一个字符串并把它打印到控制台,不返回值。

了解使用lambda表达式,需要了解函数式接口,lambda用来代替内部类,赋予了Java简单但是强大的函数式编程能力,同时可以认为java支持命令式编程、声明式编程、函数式编程。

函数式接口

public interface MyFunctionInterface<T> {
    public T getValue(T t);
}
AI 代码解读
public class MyFunctionInterfaceTest {
    public static void main(String[] args) {
        testMethod("   aaaa  ", s -> s.trim());
        testMethod("   aaaa  ", s -> s.trim().toUpperCase());
    }

    public static void testMethod(String str,     MyFunctionInterface<String> functionInterface) {
        System.out.println(functionInterface.getValue(str));
    }
}
AI 代码解读

输出结果如下:

aaaa
AAAA
AI 代码解读
public interface MyFunctionInterface<T> {
    public T getValue(T t);
    public T returnValue(T t);
}
AI 代码解读

增加一个方法之后,上面就不是函数式接口了,可以看到lambda表达式就会报错。

@FunctionalInterface
public interface MyFunctionInterface<T> {

    public T getValue(T t);

    default void defaultMethod() {
        System.out.println("this is default method");
    }

    static void staticMethod() {
        System.out.println("this is static method");
    }


    public boolean equals(Object obj);
}
AI 代码解读

我们可以在接口上使用@FunctionalInterface注解,如果使用Intellij IDEA可以在编码的时候就看见报错了,这样做可以检查它是否是一个函数式接口。

通过反编译,可以看到函数式接口其实就是一个普通的java接口类,如下图

函数式接口可以作为方法参数传递lambda表达式,但是为了将Lambda表达式作为参数传递,接收Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。但是我们没必要为每一个lambda表达式创建接口,在jdk的java.util.function包下面已经为我们创建了常用的函数式接口,其中比较核心的是消费型接口(Consumer<T>),供给型接口(Supplier<T>),断言型接口(Predicate<T>),函数型接口(Function<T, R>)四个接口,能够满足大部分应用场景。

lambda表达式原理分析

继续使用上面的测试代码,可以在IDEA中使用
jclasslib Bytecode viewer
插件查看MyFunctionInterface.class源文件。

安装完jclasslib Bytecode viewer,会在view菜单中出现如下两个选项

选择需要反编译的class文件,点击Show Bytecode with Jclasslib选项会出现如下界面

可以看到里面编译器多生成了lambdamain0和lambdamain1两个私有静态方法,属性当中多了InnerClasses。我们可以通过Show Bytecode查看一下测试类字节码更详细的反编译结果,找到这两个静态方法。

// access flags 0x100A
private static synthetic lambda$main$1(Ljava/lang/String;)Ljava/lang/String;
 L0
  LINENUMBER 6 L0
  ALOAD 0
  INVOKEVIRTUAL java/lang/String.trim ()Ljava/lang/String;
  INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;
  ARETURN
 L1
  LOCALVARIABLE s Ljava/lang/String; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1

// access flags 0x100A
private static synthetic lambda$main$0(Ljava/lang/String;)Ljava/lang/String;
 L0
  LINENUMBER 5 L0
  ALOAD 0
  INVOKEVIRTUAL java/lang/String.trim ()Ljava/lang/String;
  ARETURN
 L1
  LOCALVARIABLE s Ljava/lang/String; L0 L1 0
  MAXSTACK = 1
  MAXLOCALS = 1
}
AI 代码解读

可以看到两个私有的静态方法干的就是Lambda表达式里面的内容,那么又是如何调用的生成的私有静态方法呢?如下图,通过分析main方法的L0,首先通过INVOKEDYNAMIC 指令调用是MyFunctionInterface的getValue方法的引用,以及后面的BootstrapMethods #0。使用jclasslib Bytecode viewer查看。

点击#3,进入下面界面

点击BootstrapMethods #0,进入如下界面

点击cp_info #44,进入如下界面

继续点击相应的方法描述符,我们可以看到最后

(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
AI 代码解读

可以看到INVOKEDYNAMIC 后面的一系列指令,最后使用INVOKESTATIC调用

java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
AI 代码解读

查看LambdaMetafactory.metafactory的方法,
里面通过InnerClassLambdaMetafactory生成了CallSite的子类ConstantCallSite,当通过指令调用CallSite会返回函数式接口的实例,而生成接口实例的方式是通过内部类的方式,由于方法比较深,就不继续贴代码了。

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();
}
AI 代码解读

CallSite持有
com/para/lambda/MyFunctionInterfaceTest.lambdamain0方法的句柄,这个句柄会调用该方法。

所以使用lambda表达式的地方,会在类编译的时候在本类中生成对应的私有静态方法和一个INNERCLASS的访问标识(具体是什么东西没找到资料,注释显示是一个访问标识),该访问标识会调用引导类加载器动态生成内部类,该内部类实现了函数式接口,在实现接口的方法中,会调用编译器生成静态方法,在使用lambda表达式的地方,通过传递内部类实例,来调用函数式接口方法。

总结

本文从lambda表达式、函数式接口的介绍和对lambda表达式底层原理的分析来认识java中的函数式编程。函数式接口本身就是一个普通的接口,而lambda表达式本质上和匿名内部类是一样的,只不过条件更加苛刻。使用lamda表达式可以以一种更优雅的方式来编程。

目录
打赏
0
0
0
0
200
分享
相关文章
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
146 53
深入理解Java中的Lambda表达式与函数式编程
在Java 8中引入的Lambda表达式,为Java带来了一种全新的编程范式——函数式编程。本文将深入探讨Lambda表达式的概念、语法和实际应用场景,以及它如何改变我们编写和维护代码的方式。通过具体的例子,我们将看到Lambda表达式如何简化代码结构,提高开发效率,并使代码更加简洁易读。最后,我们将讨论Lambda表达式在多线程编程中的应用,以及它对Java开发者技能集的影响。
为什么程序员喜欢用Lambda表达式?
“lambda 表达式”是一段可以传递的代码,因此它可以被执行一次或多次。在学习语法(甚至包括一些奇怪的术语)之前,我们先回顾一下之前在Java 中一直使用的相似的代码块。
91 0
03-📝C++核心语法|面向对象1【 C++编程规范、类和对象、面向对象程序设计案例、对象的构造和析构、C++面向对象模型初探】
复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。
03-📝C++核心语法|面向对象1【 C++编程规范、类和对象、面向对象程序设计案例、对象的构造和析构、C++面向对象模型初探】
5.3 Lambda表达式在框架和库中的应用:在并发编程中使用Lambda表达式
5.3 Lambda表达式在框架和库中的应用:在并发编程中使用Lambda表达式
98 0
都2023年了,如果不会Lambda表达式、函数式编程?你确定能看懂公司代码?
都2023年了,如果你不会Lambda表达式、函数式编程?你确定能看懂公司代码? 那么建议来了解一下Lambda表达式, 因为它使用简单,易操作,易上手而代码简洁,开发快速,一看就令人很爽😎😎😎 . 其实Lambda表达式接近自然语言,易于理解 , 集万千优点与一身, 比匿名内部类更加完美👉👉👉.下面来简单认识一下今天的主角Lambda表达式吧
167 0
【精通函数式编程】(三)Lambda表达式原理与函数式接口精讲
本文讲解lambda表达式语法、语义,讲解函数式接口是什么,作用是什么,Lambda表达式的编译原理,以及Java8及高版本的函数式接口Consumer、Function 、Predicate,将会在工作中大量用到
【精通函数式编程】(三)Lambda表达式原理与函数式接口精讲
JDK中Lambda表达式的序列化与SerializedLambda的巧妙使用
笔者在下班空余时间想以Javassist为核心基于JDBC写一套摒弃反射调用的轻量级的ORM框架,过程中有研读mybatis、tk-mapper、mybatis-plus和spring-boot-starter-jdbc的源代码,其中发现了mybatis-plus中的LambdaQueryWrapper可以获取当前调用的Lambda表达式中的方法信息(实际上是CallSite的信息),这里做一个完整的记录。本文基于JDK11编写,其他版本的JDK不一定合适。
478 0
JDK中Lambda表达式的序列化与SerializedLambda的巧妙使用