首页 > 其他分享 >Lambda表达式介绍和底层实现

Lambda表达式介绍和底层实现

时间:2023-01-09 19:01:34浏览次数:42  
标签:lang Ljava String 接口 Lambda 底层 表达式 lambda

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

lambda表达式

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

(parameters) -> expression或 (parameters) ->{ statements; }

可选类型声明

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

可选的参数圆括号

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

可选的大括号

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

可选的返回关键字

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

(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }

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

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

函数式接口

public interface MyFunctionInterface<T> {
public T getValue(T t);
}
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));
}
}

输出结果如下:

aaaa
AAAA
public interface MyFunctionInterface<T> {
public T getValue(T t);
public T returnValue(T t);
}

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

Lambda表达式介绍和底层实现_java

@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);
}

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

Lambda表达式介绍和底层实现_java_02

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

Lambda表达式介绍和底层实现_函数式接口_03

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

lambda表达式原理分析

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

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

Lambda表达式介绍和底层实现_lambda表达式_04

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

Lambda表达式介绍和底层实现_java_05

可以看到里面编译器多生成了lambda$mainLambda表达式介绍和底层实现_lambda表达式_06main$1两个私有静态方法,属性当中多了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
}

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

Lambda表达式介绍和底层实现_函数式接口_07

点击#3,进入下面界面

Lambda表达式介绍和底层实现_lambda表达式_08

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

Lambda表达式介绍和底层实现_函数式接口_09

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

Lambda表达式介绍和底层实现_lambda表达式_10

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

Lambda表达式介绍和底层实现_函数式接口_11

(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;

可以看到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;

查看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();
}

Lambda表达式介绍和底层实现_java_12

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

Lambda表达式介绍和底层实现_lambda表达式_13

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

总结

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


标签:lang,Ljava,String,接口,Lambda,底层,表达式,lambda
From: https://blog.51cto.com/u_15640304/5998690

相关文章

  • C++成员初始化表达式列表的使用
    在类的构造函数中,首选使用成员初始化表达式列表去给类的对象赋值,这个比构造函数体种赋值速度更快,开销小解答如下:赋值初始化,通过在函数体内进行赋值初始化;列表初始化,在......
  • Springboot设置定时任务,从数据库中获取cron表达式 DEMO
    适用场景:需要在项目运行时改动定时任务执行时间,可将cron表达式放在缓存或者数据库中代码如下:(省略了获取cron的方法,需要自己根据情况获取) importorg.springframework.......
  • 代码随想录算法训练营第11天 | 20. 有效的括号 1047. 删除字符串中的所有相邻重复项 1
    20.有效的括号文章:代码随想录(programmercarl.com)视频:栈的拿手好戏!|LeetCode:20.有效的括号_哔哩哔哩_bilibili思路:先来分析一下这里有三种不匹配的情况,第一种......
  • 230108_50_RPC底层原理
    Stub还有很多需要优化的地方,目前只是实现了一个最基本的代理。网络传输都是通过序列化和反序列化进行的,目前java自带的Serializable接口效率比较低,因此可以对rpc的序列化......
  • P8683 [蓝桥杯 2019 省 B] 后缀表达式
    题目描述给定 NN 个加号、 MM 个减号以及 N+M+1N+M+1 个整数 A_1,A_2,\cdots,A_{N+M+1}A1​,A2​,⋯,AN+M+1​,小明想知道在所有由这 NN 个加号、 MM 个减号以......
  • java基础--lambda表达式
    lambda表达式,一种常见用法,就是简化匿名内部类。使用前提条件:如果一个方法A(),只涉及一个抽象方法待实现,那么使用A()时,涉及到匿名内部类,就可以简化为lambda表达式lambda表......
  • Gvim基础操作(正则表达式)-02
    Gvim正则表达式正则表达式在linux中使用非常广泛。主要是进行一些替换,在编写脚本的时候都会使用到。gvim、perl、sed、tcl中都会使用到。Gvim正则表达式的使用搜索命令......
  • 30 lambda表达式 知识点
    //无参无返回值Actionat=()=>{Console.WriteLine("无参无返回");};at.Invoke();//有参无返回值Action<int>st1=(intvalue)=>{......
  • .NET Core定时任务(控制台程序) cron表达式+Quartz
    .NETCore定时任务(控制台程序)https://www.cnblogs.com/Simple-520/p/15753898.html1、首先定时任务适合的程序是控制台程序2、NETCore中定时任务程序中一共三个文件(1):a......
  • 算法刷题 Day 11 | 20. 有效的括号 1047. 删除字符串中的所有相邻重复项 150. 逆波兰
    20.有效的括号讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。大家先自己思考一下有哪些不匹配的场景,在看视频我讲的都有哪些场景,落实到代码其实就容易......