18.Java Lambda 表达式(Lambda 表达式练习与原理分析、@FunctionalInterface 注解)

server/2024/12/16 16:17:25/

一、问题引入

1、问题案例
  • 开启一个新的线程,指定线程要执行的任务
java">new Thread(new Runnable() {public void run() {System.out.println("Hello World");}
}).start();
2、问题分析
  1. Thread 类需要一个 Runnable 接口作为参数,其中抽象方法 run 是用来指定线程的核心任务

  2. 为了实现 run 方法的方法体,不得不需要 Runnable 实现类

  3. 为了省去定义一个 Runnable 实现类,不得不使用匿名内部类

  4. 必须重写 run 方法,那么方法名称、返回值、参数列表等都不得不重写,而实际上,我们只在乎方法体


二、Lambda 表达式引入

1、初体验
  • Lambda 表达式是一个匿名函数,可以理解为一段可传递的代码
java">new Thread(() -> {System.out.println("Hello Lambda");
}).start();
2、基本介绍
  1. Lambda 表达式简化了匿名内部类冗余的语法使用,语法更简单

  2. 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 文件


http://www.ppmy.cn/server/150673.html

相关文章

力扣-图论-9【算法学习day.59】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

uniapp跨端适配—条件编译

在uniapp中&#xff0c;跨端适配是通过条件编译实现的。条件编译允许开发者根据不同的平台&#xff08;如iOS、Android、微信小程序、百度小程序等&#xff09;编写不同的代码。这样可以确保每个平台上的应用都能得到最优的性能和用户体验。 以下是uniapp中条件编译的基本语法…

【多模态实战】在本地计算机上使用小型视觉语言模型【VLM】进行目标计数【附源码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

Java面试之多线程安全(四)

此篇接上一篇Java面试之多线程状态(三) 对于线程安全问题&#xff0c;想从是什么、为什么、怎么做三个方面来整理这篇知识点。 首先&#xff0c;在《Java并发编程实战》书中&#xff0c;Brian Goetz大神是这样定义什么是线程安全性的。 当多个线程访问某个类时&#xff0c;不管…

若依-帝可得app后端

视频地址 https://www.bilibili.com/video/BV1pf421B71v?t=510.1 APP后端技术栈 架构解析 验证码功能 开发环境使用改的是固定的验证码 12345正式环境使用的是 阿里云的短信方案@Override public void sendSms(String mobile) {// String code = RandomUtil.randomNumbers(5);…

相位小数偏差(UPD)估计基本原理

PPP中的一个关键性难题在于非差模糊度固定&#xff0c;成功固定非差模糊度可以使 PPP 的收敛速度和定位精度得到显著提升 。 相位小数偏差 (UPD) 是致使相位模糊度失去整数特性的主要因素&#xff0c;精确估计并校正 UPD 是实现非差模糊度固定的重要前提&#xff0c;也是实现…

【深度学习】 零基础介绍卷积神经网络(CNN)

零基础介绍 卷积神经网络&#xff08;CNN&#xff0c;Convolutional Neural Network&#xff09;是深度学习中的一种神经网络&#xff0c;特别擅长处理图像和视频等有空间结构的数据。 假设我们在做一个“照片分类”的任务&#xff0c;比如判断一张照片中是猫还是狗。下面用一…

【读书】试说中台之一

个人思考系列&#xff0c;开放题目&#xff0c;供后续复盘 起源 2015年12月7日&#xff0c;阿里巴巴全面启动中台战略&#xff0c;开启“大中台&#xff0c;小前台”时代。由此开始&#xff0c;中台的概念迅速火遍全网&#xff0c;成为一时新宠&#xff0c;各个企业都开始建立…