初体验
什么是lambda表达式呢?不墨迹,直接上最经典的线程来进行初体验
没使用lambda表达式时,我们创建线程的写法
package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
public static void main(String[] args) {
// 普通写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程被创建:"+Thread.currentThread().getName());
}
}).start();
}
}
复制代码
代码分析,发现问题
- Thread类需要一个Runnable接口做为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的实现类
- 为了省去定义一个Runnable的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不重写一遍,而且不能出错,但是,我们只关心run方法体内的代码
- 因此,lambda应这个问题而产生
lambda表达式初体验–创建新线程
package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
public static void main(String[] args) {
// lambda写法
new Thread(()->{
System.out.println("新线程被创建:"+Thread.currentThread().getName());
}).start();
}
}
复制代码
- 可以发现
- lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
- lambda表达式简化了匿名内部类的使用,语法更加简单。
- lambda表达式使得代码更加的简洁
lambda表达式语法
lambda表达式组成
lambda表达式由三部分组成: () -> {}
(参数列表) -> {方法体代码}
复制代码
()
中填参数->
固定写法,分割参数列表和方法体代码{}
中编写我们方法体的代码
lambda表达式的使用条件
- 方法的参数或者局部变量类型必须为接口才能使用
- 接口中有且仅有一个抽象方法(
@FunctionalInterface
)
@FunctionalInterface注解
-
一种信息性注释类型,用于指示接口类型声明是Java语言规范所定义的功能接口。从概念上讲,一个功能性接口只有一个抽象方法。因为默认方法有一个实现,所以它们不是抽象的。如果接口声明了一个抽象方法,则该方法将覆盖java.lang的一个公共方法。对象,这也不计入接口的抽象方法计数,因为接口的任何实现都将有来自java.lang.Object或其他地方的实现。
-
注意,函数接口的实例可以用lambda表达式、方法引用或构造函数引用来创建。
-
如果一个类型是用这个注释类型注释的,编译器需要生成一个错误消息,除非:
- 该类型是接口类型,而不是注释类型、枚举或类。
- 带注释的类型满足功能性接口的需求。
-
但是,编译器将把任何满足函数接口定义的接口视为函数接口,而不管接口声明中是否存在FunctionalInterface注释。
总而言之,@FunctionalInterface
注解就是用来保证该接口只允许定义一个抽象方法
lambda表达式缩写
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号
就好比如在创建线程的时候
new Thread(
() -> System.out.println("新线程被创建:"+Thread.currentThread().getName())
).start();
复制代码
lambda vs 匿名内部类
- 所需类型不一样
- 匿名内部类的类型可以是类,抽象类,接口
- Lambda表达式需要的类型必须是接口
- 抽象方法的数量不—样
- 匿名内部类所需的接口中的抽象方法的数量是随意的
- Lambda表达式所需的接口中只能有一个抽象方法
- 实现原理不—样
- 匿名内部类是在编译后形成一个class
- Lambda表达式是在程序运行的时候动态生成class
lambda原理分析
我们以创建线程为例进行分析
方式一
package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程被创建:"+Thread.currentThread().getName());
}
}).start();
}
}
复制代码
方式二
package com.features.lambda.demo01;
// 通过线程来初步认识lambda表达式
public class demo01 {
public static void main(String[] args) {
new Thread(()->{
System.out.println("新线程被创建:"+Thread.currentThread().getName());
}).start();
}
}
复制代码
运行,通过方式一会生成demo01$1.class
和demo01.class
两个文件,其中,demo01$1.class
文件是由匿名内部类产生的,demo01$1.class
文件是匿名内部类的本质实现;而通过方式二运行,则只会生成demo01.class
,这就说明了lambda表达式并未生成匿名内部类class文件,也就是说java8并不是靠编译器将lambda转换为匿名内部类
这时候,我们通过JDK自带的工具javap对demo01.class
进行反编译(注意,要找到demo01.class
的文件位置,在那里执行命令)
输入命令进行反编译
javap -c -p 文件名.class
-c 对代码进行反编译 (-c也可以换成-v,-v的信息更加详细)
-p 显示所有的类和成员
复制代码
反编译的结果:
D:\Users\ASUS\JDK8-new-features\target\classes\com\features\lambda\demo01>javap -c -p demo01.class
Compiled from "demo01.java"
public class com.features.lambda.demo01.demo01 {
public com.features.lambda.demo01.demo01();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
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: return
private static void lambda$main$0();
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #7 // class java/lang/StringBuilder
6: dup
7: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
10: ldc #9 // String 新线程被创建:
12: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokestatic #11 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
18: invokevirtual #12 // Method java/lang/Thread.getName:()Ljava/lang/String;
21: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
}
复制代码
在反编译的结果中,我们看到了它创建了一个private static void lambda$main$0();
方法,其实,这应该就是lambda表达式所对应的方法,我们可以通过debug的方式进行测试
也就是相当于lambda$main$0()
静态方法如下
private static void lambda$main$0(){
System.out.println("新线程被创建:"+Thread.currentThread().getName());
}
复制代码
为了更加直观的理解这个它,我们可以在运行的时候添加Java -Djdk.internallambda.dumpProxyClasses 要运行的包名.类名
命令,加上这个参数会将内部class码输出到一个文件中(注意,该命令运行的位置是在classes
的位置)
D:\Users\ASUS\JDK8-new-features\target\classes>java -Djdk.internallambda.dumpProxyClasses com.features.lambda.demo01.demo01
复制代码
结果:
小结:
匿名内部类在编译的时候会产生—个class文件。
Lambda表达式在程序运行的时候会形成一个类。
- 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码,即
private static void lambda$main$0()
方法 - 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口中重写方法会调用新生成的方法