一、问题引入
1、问题案例
- 开启一个新的线程,指定线程要执行的任务
java">new Thread(new Runnable() {public void run() {System.out.println("Hello World");}
}).start();
2、问题分析
-
Thread 类需要一个 Runnable 接口作为参数,其中抽象方法 run 是用来指定线程的核心任务
-
为了实现 run 方法的方法体,不得不需要 Runnable 实现类
-
为了省去定义一个 Runnable 实现类,不得不使用匿名内部类
-
必须重写 run 方法,那么方法名称、返回值、参数列表等都不得不重写,而实际上,我们只在乎方法体
二、Lambda 表达式引入
1、初体验
- Lambda 表达式是一个匿名函数,可以理解为一段可传递的代码
java">new Thread(() -> {System.out.println("Hello Lambda");
}).start();
2、基本介绍
-
Lambda 表达式简化了匿名内部类冗余的语法使用,语法更简单
-
Lambda 表达式的标准格式如下,由三个部分组成
java">(【参数类型】 【参数名称】) -> {【方法体】;
}
三、Lambda 表达式练习
1、无参无返回值
- 定义 UserService 接口
java">package com.my.lambda.service;public interface UserService {void show();
}
- 测试
java">package com.my.lambda.test;import com.my.lambda.service.UserService;public class LambdaTest2 {public static void main(String[] args) {// 匿名内部类goShow(new UserService() {@Overridepublic 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 类
java">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;
}
- 测试
java">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 进行查看
java">// 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.javapackage com.my.lambda.test;import java.io.PrintStream;// Referenced classes of package com.my.lambda.test:
// LambdaTest1static 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_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: new #2 // class com/sunke/lambda/test/LambdaTest2$13: dup4: invokespecial #3 // Method com/sunke/lambda/test/LambdaTest2$1."<init>":()V7: invokestatic #4 // Method goShow:(Lcom/sunke/lambda/service/UserService;)V10: invokedynamic #5, 0 // InvokeDynamic #0:show:()Lcom/sunke/lambda/service/UserService;15: invokestatic #4 // Method goShow:(Lcom/sunke/lambda/service/UserService;)V18: returnpublic static void goShow(com.my.lambda.service.UserService);Code:0: aload_01: invokeinterface #6, 1 // InterfaceMethod com/sunke/lambda/service/UserService.show:()V6: returnprivate static void lambda$main$0();Code:0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #8 // String Lambda Hello Show5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return
}
- 上面的效果可以这样理解,在类中生产了一个新的方法 lambda$main$0
java">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 文件
java">// 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:
// LambdaTest2final class LambdaTest2$$Lambda$1 implements UserService {public void show() {LambdaTest2.lambda$main$0();}private LambdaTest2$$Lambda$1() {}
}
- 生成一个匿名内部类,实现 UserService 接口,重写 show 方法并调用了 LambdaTest2.lambda$main$0()
java">package com.my.lambda.test;import com.my.lambda.service.UserService;public class LambdaTest2 {public static void main(String[] args) {goShow(new UserService() {@Overridepublic 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 接口
java">package com.my.lambda.service;@FunctionalInterface
public interface StudentService {String say(String name, int age);
}
- 定义 Teacher 接口
java">package com.my.lambda.service;@FunctionalInterface
public interface TeacherService {void say(String name);
}
- 测试
java">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 文件
-