深入理解lambda的奥秘

Posted 李哥技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解lambda的奥秘相关的知识,希望对你有一定的参考价值。

大家好,我是李哥。

JDK1.6和JDK1.8是主流的两个大版本,目前市场上用的最多最多的依然是JDK1.8。所以,我们有必要聊一聊Java8的一些新特性。

  1. 深入理解lambda的奥秘
  2. 深入理解Stream之原理剖析
  3. 深入理解Stream之foreach源码解析
  4. 深入浅出NPE神器Optional
  5. 谈谈接口默认方法与静态方法
  6. 深入浅出重复注解与类型注解
  7. 深入浅出JVM元空间metaspace

今天我们先来聊聊深入理解lambda的奥秘

深入理解lambda的奥秘_代码块


为什么会出现函数式编程

在数学中,函数是有输入量、输出量的一套计算方案。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。

面向对象编程是对数据进行抽象;而函数式编程是对行为进行抽象。


lambda表达式是什么

lambda表达式也可称为闭包,是一个匿名函数,是对匿名函数的简写形式,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递),可以写出更简洁更灵活的代码。

作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。


lambda表达式目标定位

lambda表达式的目标定位:预定义使用了 @FunctionalInterface 注释的函数式接口,自带一个未实现的函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。

例如,若一个方法接收Runnable、Comparator或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。

类似的,如果一个方法接受声明于 java.util.function 包内的接口,里面的每一个接口都标明类注解@FunctionalInterface,并且只有一个未实现的函数。例如常见的 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。


lambda表达式写法组成

  1. 形参列表:形参列表允许省略类型,如果形参列表中只有一个参数,形参列表的圆括号也可以省略;
  2. 箭头(->):通过英文画线和大于符号组成;
  3. 代码块:如果代码块只有一条语句,花括号可以省略。Lambda 代码块只有一条 return 语句,可以省略 return 关键字,Lambda 表达式会自动返回这条语句的值作为返回值。

说到这里,相信大家已经比较明确lambda表达式了。接下来我们看看如何运用它吧。


lambda的常见使用

深入理解lambda的奥秘_lambda表达式_02


更深入的理解

先来看几个case

深入理解lambda的奥秘_java_03

在编译器内部,会将lambda表达式翻译成私有方法,执行invokedynamic指令。我们可以使用javap -c -v查看字节码文件,当然也可以使用Idea的插件来查看字节码。

jclasslib plugin:

深入理解lambda的奥秘_javaee_04

javap -c -v:(代码块较长,不想关注细节可直接跳过此模块)

Classfile /Users/lige/IdeaProjects/javahomeProject/out/production/javahomeProject/com/ligejishu/lambda/LambdaDemo02.class
Last modified 2022-8-7; size 2654 bytes
MD5 checksum c76a7d3ce0088c93f0dc265b8eee28de
Compiled from "LambdaDemo02.java"
public class com.ligejishu.lambda.LambdaDemo02
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #30.#54 // java/lang/Object."<init>":()V
#2 = Class #55 // java/lang/Thread
#3 = InvokeDynamic #0:#60 // #0:run:()Ljava/lang/Runnable;
#4 = Methodref #2.#61 // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
#5 = Methodref #2.#62 // java/lang/Thread.start:()V
#6 = Class #63 // java/util/ArrayList
#7 = Methodref #6.#54 // java/util/ArrayList."<init>":()V
#8 = String #64 // a
#9 = InterfaceMethodref #17.#65 // java/util/List.add:(Ljava/lang/Object;)Z
#10 = String #66 // c
#11 = String #67 // b
#12 = InterfaceMethodref #17.#68 // java/util/List.stream:()Ljava/util/stream/Stream;
#13 = InvokeDynamic #1:#72 // #1:test:(Ljava/lang/String;)Ljava/util/function/Predicate;
#14 = InterfaceMethodref #73.#74 // java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
#15 = Methodref #75.#76 // java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
#16 = InterfaceMethodref #73.#77 // java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
#17 = Class #78 // java/util/List
#18 = InvokeDynamic #2:#82 // #2:compare:()Ljava/util/Comparator;
#19 = InterfaceMethodref #17.#83 // java/util/List.sort:(Ljava/util/Comparator;)V
#20 = InvokeDynamic #3:#87 // #3:accept:()Ljava/util/function/Consumer;
#21 = InterfaceMethodref #17.#88 // java/util/List.forEach:(Ljava/util/function/Consumer;)V
#22 = Fieldref #89.#90 // java/lang/System.out:Ljava/io/PrintStream;
#23 = String #91 // 123,
#24 = Methodref #92.#93 // java/io/PrintStream.print:(Ljava/lang/String;)V
#25 = String #94 // ,
#26 = Methodref #92.#95 // java/io/PrintStream.println:()V
#27 = String #96 // test print.
#28 = Methodref #92.#97 // java/io/PrintStream.println:(Ljava/lang/String;)V
#29 = Class #98 // com/ligejishu/lambda/LambdaDemo02
#30 = Class #99 // java/lang/Object
#31 = Utf8 <init>
#32 = Utf8 ()V
#33 = Utf8 Code
#34 = Utf8 LineNumberTable
#35 = Utf8 LocalVariableTable
#36 = Utf8 this
#37 = Utf8 Lcom/ligejishu/lambda/LambdaDemo02;
#38 = Utf8 main
#39 = Utf8 ([Ljava/lang/String;)V
#40 = Utf8 args
#41 = Utf8 [Ljava/lang/String;
#42 = Utf8 list
#43 = Utf8 Ljava/util/List;
#44 = Utf8 filterList
#45 = Utf8 LocalVariableTypeTable
#46 = Utf8 Ljava/util/List<Ljava/lang/String;>;
#47 = Utf8 lambda$main$1
#48 = Utf8 (Ljava/lang/String;)V
#49 = Utf8 it
#50 = Utf8 Ljava/lang/String;
#51 = Utf8 lambda$main$0
#52 = Utf8 SourceFile
#53 = Utf8 LambdaDemo02.java
#54 = NameAndType #31:#32 // "<init>":()V
#55 = Utf8 java/lang/Thread
#56 = Utf8 BootstrapMethods
#57 = MethodHandle #6:#100 // 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;
#58 = MethodType #32 // ()V
#59 = MethodHandle #6:#101 // invokestatic com/ligejishu/lambda/LambdaDemo02.lambda$main$0:()V
#60 = NameAndType #102:#103 // run:()Ljava/lang/Runnable;
#61 = NameAndType #31:#104 // "<init>":(Ljava/lang/Runnable;)V
#62 = NameAndType #105:#32 // start:()V
#63 = Utf8 java/util/ArrayList
#64 = Utf8 a
#65 = NameAndType #106:#107 // add:(Ljava/lang/Object;)Z
#66 = Utf8 c
#67 = Utf8 b
#68 = NameAndType #108:#109 // stream:()Ljava/util/stream/Stream;
#69 = MethodType #107 // (Ljava/lang/Object;)Z
#70 = MethodHandle #5:#110 // invokevirtual java/lang/String.equals:(Ljava/lang/Object;)Z
#71 = MethodType #111 // (Ljava/lang/String;)Z
#72 = NameAndType #112:#113 // test:(Ljava/lang/String;)Ljava/util/function/Predicate;
#73 = Class #114 // java/util/stream/Stream
#74 = NameAndType #115:#116 // filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
#75 = Class #117 // java/util/stream/Collectors
#76 = NameAndType #118:#119 // toList:()Ljava/util/stream/Collector;
#77 = NameAndType #120:#121 // collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
#78 = Utf8 java/util/List
#79 = MethodType #122 // (Ljava/lang/Object;Ljava/lang/Object;)I
#80 = MethodHandle #5:#123 // invokevirtual java/lang/String.compareTo:(Ljava/lang/String;)I
#81 = MethodType #124 // (Ljava/lang/String;Ljava/lang/String;)I
#82 = NameAndType #125:#126 // compare:()Ljava/util/Comparator;
#83 = NameAndType #127:#128 // sort:(Ljava/util/Comparator;)V
#84 = MethodType #129 // (Ljava/lang/Object;)V
#85 = MethodHandle #6:#130 // invokestatic com/ligejishu/lambda/LambdaDemo02.lambda$main$1:(Ljava/lang/String;)V
#86 = MethodType #48 // (Ljava/lang/String;)V
#87 = NameAndType #131:#132 // accept:()Ljava/util/function/Consumer;
#88 = NameAndType #133:#134 // forEach:(Ljava/util/function/Consumer;)V
#89 = Class #135 // java/lang/System
#90 = NameAndType #136:#137 // out:Ljava/io/PrintStream;
#91 = Utf8 123,
#92 = Class #138 // java/io/PrintStream
#93 = NameAndType #139:#48 // print:(Ljava/lang/String;)V
#94 = Utf8 ,
#95 = NameAndType #140:#32 // println:()V
#96 = Utf8 test print.
#97 = NameAndType #140:#48 // println:(Ljava/lang/String;)V
#98 = Utf8 com/ligejishu/lambda/LambdaDemo02
#99 = Utf8 java/lang/Object
#100 = Methodref #141.#142 // 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;
#101 = Methodref #29.#143 // com/ligejishu/lambda/LambdaDemo02.lambda$main$0:()V
#102 = Utf8 run
#103 = Utf8 ()Ljava/lang/Runnable;
#104 = Utf8 (Ljava/lang/Runnable;)V
#105 = Utf8 start
#106 = Utf8 add
#107 = Utf8 (Ljava/lang/Object;)Z
#108 = Utf8 stream
#109 = Utf8 ()Ljava/util/stream/Stream;
#110 = Methodref #144.#145 // java/lang/String.equals:(Ljava/lang/Object;)Z
#111 = Utf8 (Ljava/lang/String;)Z
#112 = Utf8 test
#113 = Utf8 (Ljava/lang/String;)Ljava/util/function/Predicate;
#114 = Utf8 java/util/stream/Stream
#115 = Utf8 filter
#116 = Utf8 (Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
#117 = Utf8 java/util/stream/Collectors
#118 = Utf8 toList
#119 = Utf8 ()Ljava/util/stream/Collector;
#120 = Utf8 collect
#121 = Utf8 (Ljava/util/stream/Collector;)Ljava/lang/Object;
#122 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)I
#123 = Methodref #144.#146 // java/lang/String.compareTo:(Ljava/lang/String;)I
#124 = Utf8 (Ljava/lang/String;Ljava/lang/String;)I
#125 = Utf8 compare
#126 = Utf8 ()Ljava/util/Comparator;
#127 = Utf8 sort
#128 = Utf8 (Ljava/util/Comparator;)V
#129 = Utf8 (Ljava/lang/Object;)V
#130 = Methodref #29.#147 // com/ligejishu/lambda/LambdaDemo02.lambda$main$1:(Ljava/lang/String;)V
#131 = Utf8 accept
#132 = Utf8 ()Ljava/util/function/Consumer;
#133 = Utf8 forEach
#134 = Utf8 (Ljava/util/function/Consumer;)V
#135 = Utf8 java/lang/System
#136 = Utf8 out
#137 = Utf8 Ljava/io/PrintStream;
#138 = Utf8 java/io/PrintStream
#139 = Utf8 print
#140 = Utf8 println
#141 = Class #148 // java/lang/invoke/LambdaMetafactory
#142 = NameAndType #149:#153 // 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;
#143 = NameAndType #51:#32 // lambda$main$0:()V
#144 = Class #154 // java/lang/String
#145 = NameAndType #155:#107 // equals:(Ljava/lang/Object;)Z
#146 = NameAndType #156:#157 // compareTo:(Ljava/lang/String;)I
#147 = NameAndType #47:#48 // lambda$main$1:(Ljava/lang/String;)V
#148 = Utf8 java/lang/invoke/LambdaMetafactory
#149 = Utf8 metafactory
#150 = Class #159 // java/lang/invoke/MethodHandles$Lookup
#151 = Utf8 Lookup
#152 = Utf8 InnerClasses
#153 = Utf8 (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;
#154 = Utf8 java/lang/String
#155 = Utf8 equals
#156 = Utf8 compareTo
#157 = Utf8 (Ljava/lang/String;)I
#158 = Class #160 // java/lang/invoke/MethodHandles
#159 = Utf8 java/lang/invoke/MethodHandles$Lookup
#160 = Utf8 java/lang/invoke/MethodHandles

public com.ligejishu.lambda.LambdaDemo02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ligejishu/lambda/LambdaDemo02;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: invokevirtual #5 // Method java/lang/Thread.start:()V
15: new #6 // class java/util/ArrayList
18: dup
19: invokespecial #7 // Method java/util/ArrayList."<init>":()V
22: astore_1
23: aload_1
24: ldc #8 // String a
26: invokeinterface #9, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
31: pop
32: aload_1
33: ldc #10 // String c
35: invokeinterface #9, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
40: pop
41: aload_1
42: ldc #11 // String b
44: invokeinterface #9, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
49: pop
50: aload_1
51: invokeinterface #12, 1 // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream;
56: ldc #8 // String a
58: invokedynamic #13, 0 // InvokeDynamic #1:test:(Ljava/lang/String;)Ljava/util/function/Predicate;
63: invokeinterface #14, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
68: invokestatic #15 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
71: invokeinterface #16, 2 // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
76: checkcast #17 // class java/util/List
79: astore_2
80: aload_1
81: invokedynamic #18, 0 // InvokeDynamic #2:compare:()Ljava/util/Comparator;
86: invokeinterface #19, 2 // InterfaceMethod java/util/List.sort:(Ljava/util/Comparator;)V
91: aload_1
92: invokedynamic #20, 0 // InvokeDynamic #3:accept:()Ljava/util/function/Consumer;
97: invokeinterface #21, 2 // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
102: return
LineNumberTable:
line 16: 0
line 19: 15
line 20: 23
line 21: 32
line 22: 41
line 23: 50
line 26: 80
line 27: 91
line 34: 102
LocalVariableTable:
Start Length Slot Name Signature
0 103 0 args [Ljava/lang/String;
23 80 1 list Ljava/util/List;
80 23 2 filterList Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
23 80 1 list Ljava/util/List<Ljava/lang/String;>;
80 23 2 filterList Ljava/util/List<Ljava/lang/String;>;

SourceFile: "LambdaDemo02.java"
InnerClasses:
public static final #151= #150 of #158; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #57 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;
Method arguments:
#58 ()V
#59 invokestatic com/ligejishu/lambda/LambdaDemo02.lambda$main$0:()V
#58 ()V
1: #57 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;
Method arguments:
#69 (Ljava/lang/Object;)Z
#70 invokevirtual java/lang/String.equals:(Ljava/lang/Object;)Z
#71 (Ljava/lang/String;)Z
2: #57 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;
Method arguments:
#79 (Ljava/lang/Object;Ljava/lang/Object;)I
#80 invokevirtual java/lang/String.compareTo:(Ljava/lang/String;)I
#81 (Ljava/lang/String;Ljava/lang/String;)I
3: #57 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;
Method arguments:
#84 (Ljava/lang/Object;)V
#85 invokestatic com/ligejishu/lambda/LambdaDemo02.lambda$main$1:(Ljava/lang/String;)V
#86 (Ljava/lang/String;)V

由此可见,如果使用lambda表达式使用方法引用,则编译器不会生成私有方法;如果lambda表达式没有使用方法引用,而是自定义函数实现,则编译器会生成一个内部私有方法。

在这个示例代码块上,能够发现case1模块与case3的foreach模块,都是自定义了函数实现,所以编译器生成了两个私有方法,名称分别为:<lambda$mainmain$1>



总结

  1. lambda表达式强调具体实现,而不是用什么形式做
  2. lambda表达式使得代码更简洁、灵活
  3. lambda表达式也称之为闭包、匿名函数
  4. 一般情况下可使用lambda表达式的接口都会标明类注解@FunctionalInterface,当然,不标明也没问题
  5. 一般情况下如果使用lambda表达式,使用 java.util.function 包下的接口大多数都能满足要求
  6. 使用lambda表达式,如果是自定义函数编译器在编译阶段会生成内部私有方法,如果是使用方法引用则不会生成内部私有方法
  7. lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。

谈到lambda表达式,我们对于Stream需要单独拎出来聊一聊:

​深入理解Stream之原理剖析​

​深入理解Stream之foreach源码解析​

深入理解lambda的奥秘_Stream_06

以上是关于深入理解lambda的奥秘的主要内容,如果未能解决你的问题,请参考以下文章

理解性能的奥秘——应用程序中慢,SSMS中快——不总是参数嗅探的错

深入理解Stream之原理剖析

深入理解Stream之foreach源码解析

yes带你深入算法与中间件设计的奥秘,源码层面

C语言初阶深入探索C语言操作符的奥秘(下)!!

理解性能的奥秘——应用程序中慢,SSMS中快——案例:如何应对参数嗅探