一、问题引入
1、问题案例
- 开启一个新的线程,指定线程要执行的任务
new Thread(new Runnable() {
public void run() {
System.out.println("Hello World");
}
}).start();
2、问题分析
-
Thread 类需要一个 Runnable 接口作为参数,其中抽象方法 run 是用来指定线程的核心任务
-
为了实现 run 方法的方法体,不得不需要 Runnable 实现类
-
为了省去定义一个 Runnable 实现类,不得不使用匿名内部类
-
必须重写 run 方法,那么方法名称、返回值、参数列表等都不得不重写,而实际上,我们只在乎方法体
二、Lambda 表达式引入
1、初体验
- Lambda 表达式是一个匿名函数,可以理解为一段可传递的代码
new Thread(() -> {
System.out.println("Hello Lambda");
}).start();
2、基本介绍
-
Lambda 表达式简化了匿名内部类冗余的语法使用,语法更简单
-
Lambda 表达式的标准格式如下,由三个部分组成
(【参数类型】 【参数名称】) -> {
【方法体】;
}
三、Lambda 表达式练习
1、无参无返回值
- 定义 UserService 接口
package com.my.lambda.service;
public interface UserService {
void show();
}
- 测试
package com.my.lambda.test;
import com.my.lambda.service.UserService;
public class LambdaTest2 {
public static void main(String[] args) {
// 匿名内部类
goShow(new UserService() {
@Override
public void show() {
System.out.println("Hello Show");
}
});
// Lambda 表达式
goShow(() -> {
System.out.println("Lambda Hello Show");
});
}
public static void goShow(UserService userService) {
userService.show();
}
}
2、有参有返回值
- 定义 User 类
package com.my.lambda.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private int age;
}
- 测试
package com.my.lambda.test;
import com.my.lambda.entity.User;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
public class LambdaTest3 {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
users.add(new User("jack", 10));
users.add(new User("smith", 15));
users.add(new User("tom", 5));
// // 匿名内部类
// Collections.sort(users, new Comparator<User>() {
// @Override
// public int compare(User o1, User o2) {
// return o1.getAge() - o2.getAge();
// }
// });
// Lambda 表达式
Collections.sort(users, (User o1, User o2) -> {
return o1.getAge() - o2.getAge();
});
Iterator<User> iterator = users.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
System.out.println(user);
}
}
}
四、@FunctionalInterface 注解
1、基本介绍
- 被该注解修饰的接口只能声明一个抽象方法
2、应用场景
- 使用 Lambda 表达式实现的接口只能存在一个抽象方法
五、原理分析
1、XJad
(1)XJad 下载
- 下载地址:http://www.ucbug.com/soft/129590.html
(2)XJad 安装
- 解压缩
(3)XJad 使用
- 直接将 class 文件拖入
2、匿名内部类原理
-
匿名内部类的本质是在编译时生成一个 class 文件,名称为 【XXX】$【XXX】.class(例如:LambdaTest1$1.class)
-
可以使用反编译工具 XJad 进行查看
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: LambdaTest1.java
package com.my.lambda.test;
import java.io.PrintStream;
// Referenced classes of package com.my.lambda.test:
// LambdaTest1
static class LambdaTest1$1 implements Runnable {
public void run() {
System.out.println("Hello World");
}
LambdaTest1$1() {}
}
3、JDK 的反编译工具
-
写有 Lambda 表达式的 class 文件,无法使用 XJad 进行查看
-
可以使用 JDK 自带的 javap 对class 文件进行反编译操作
javap -c -p 【class 文件】
- 生成一个 Lambda 表达式对应的 class 文件
java -Djdk.internal.lambda.dumpProxyClasses 【全类名】
4、Lambda 表达式原理
- 使用 javap 对 LambdaTest2.class 文件进行反编译操作
D:\javaCode\JDK8Test\target\classes\com\sunke\lambda\test>javap -c -p LambdaTest2.class
Compiled from "LambdaTest2.java"
public class com.my.lambda.test.LambdaTest2 {
public com.my.lambda.test.LambdaTest2();
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 com/sunke/lambda/test/LambdaTest2$1
3: dup
4: invokespecial #3 // Method com/sunke/lambda/test/LambdaTest2$1."<init>":()V
7: invokestatic #4 // Method goShow:(Lcom/sunke/lambda/service/UserService;)V
10: invokedynamic #5, 0 // InvokeDynamic #0:show:()Lcom/sunke/lambda/service/UserService;
15: invokestatic #4 // Method goShow:(Lcom/sunke/lambda/service/UserService;)V
18: return
public static void goShow(com.my.lambda.service.UserService);
Code:
0: aload_0
1: invokeinterface #6, 1 // InterfaceMethod com/sunke/lambda/service/UserService.show:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String Lambda Hello Show
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
- 上面的效果可以这样理解,在类中生产了一个新的方法 lambda$main$0
package com.my.lambda.test;
import com.my.lambda.service.UserService;
public class LambdaTest2 {
public static void main(String[] args) {
---
}
public static void goShow(UserService userService) {
userService.show();
}
private static void lambda$main$0() {
System.out.println("Lambda Hello Show");
}
}
- 生成 LambdaTest2.class 中 Lambda 表达式对应的 class 文件(注意目录)
D:\javaCode\JDK8Test\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.my.lambda.test.LambdaTest2
Hello Show
Lambda Hello Show
- 使用 XJad 查看 LambdaTest2$$Lambda$1.class 文件
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
package com.my.lambda.test;
import com.my.lambda.service.UserService;
// Referenced classes of package com.my.lambda.test:
// LambdaTest2
final class LambdaTest2$$Lambda$1 implements UserService {
public void show() {
LambdaTest2.lambda$main$0();
}
private LambdaTest2$$Lambda$1() {}
}
- 生成一个匿名内部类,实现 UserService 接口,重写 show 方法并调用了 LambdaTest2.lambda$main$0()
package com.my.lambda.test;
import com.my.lambda.service.UserService;
public class LambdaTest2 {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
LambdaTest2.lambda$main\$0();
}
});
}
public static void goShow(UserService userService) {
userService.show();
}
private static void lambda$main$0() {
System.out.println("Lambda Hello Show");
}
}
5、小结
(1) 匿名内部类的本质
- 编译时生成一个 class 文件
(2)Lambda 表达式的本质
-
在类中生成一个新方法,该方法的参数列表和方法体就是 Lambda 表达式的参数列表和方法体
-
在类中生成一个匿名内部类,实现接口并重写抽象方法,重写的抽象方法会调用新生成的方法
六、Lambda 表达式简写
1、简写规则
-
小括号内的参数类型可以省略
-
如果小括号内有且仅有一个参数,那么可以省略小括号
-
如果大括号内有且仅有一条语句,那么可以同时省略大括号、return 关键字和语句分号
2、测试
- 定义 Student 接口
package com.my.lambda.service;
@FunctionalInterface
public interface StudentService {
String say(String name, int age);
}
- 定义 Teacher 接口
package com.my.lambda.service;
@FunctionalInterface
public interface TeacherService {
void say(String name);
}
- 测试
package com.my.lambda.test;
import com.my.lambda.service.StudentService;
import com.my.lambda.service.TeacherService;
public class LambdaTest4 {
public static void main(String[] args) {
goStudentSay((name, age) -> "我是学生,我的名字是" + name + ", 我今年 + " + age + " 岁");
goTeacherSay(name -> System.out.println("我是老师,我的名字是" + name));
}
public static void goStudentSay(StudentService studentService) {
System.out.println(studentService.say("jack", 20));
}
public static void goTeacherSay(TeacherService teacherService) {
teacherService.say("tom");
}
}
七、总结
1、Lambda 表达式的使用前提
-
方法的参数类型或变量的类型必须为接口
-
接口中有且仅有一个抽象方法
2、Lambda 表达式对比匿名内部类
-
类型不同
-
匿名内部类所需类型可以是类、抽象类、接口
-
Lambda 表达式所需类型必须是接口
-
-
抽象方法数量不同
-
匿名内部类对抽象方法数量没有要求
-
Lambda 表达式所需的接口中只能存在一个抽象方法
-
-
实现原理不同
-
匿名内部类在编译后生成 class 文件
-
Lambda 表达式在程序运行时动态生成 class 文件
-