为什么要使用lambda表达式?原来如此,涨知识了
mhr18 2025-01-11 15:31 18 浏览 0 评论
为什么要使用Lambda表达式
先看几段Java8以前经常会遇到的代码:
创建线程并启动
// 创建线程
public class Worker implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
doWork();
}
}
}
// 启动线程
Worker w = new Worker();
new Thread(w).start();
比较数组
// 定义一个比较器
public class LengthComparator implements Comparator<String> {
@Override
public int compare(String first, String second) {
return Integer.compare(first.length(), second.length());
}
}
//对字符数组进行比较
Arrays.sort(words, new LengthComparator());
给按钮添加单击事件
public void onClick(Button button) {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked.");
}
});
}
对于这三段代码,我们已经司空见惯了。
但他们的问题也很突出:就是噪声太多!想实现一个数组的比较功能,至少要写5行代码,但其中只有一行代码才是我们真正关注的!
Java复杂冗余的代码实现一直被程序员所诟病,好在随着JVM平台语言Scala的兴起以及函数式编程风格的风靡,让Oracle在Java的第8个系列版本中进行了革命性的变化,推出了一系列函数式编程风格的语法特性,比如Lambda表达式以及Stream。
如果采用Lambda表达式,上面三段代码的实现将会变得极为简洁。
创建线程并启动(采用Lambda版本)
new Thread(() -> {
for (int i = 0; i < 100; i++) {
doWork();
}
}).start();
比较数组(采用Lambda版本)
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())
给按钮添加单击事件(采用Lambda版本)
button.addActionListener((event) -> System.out.println("button clicked."));
怎么样?通过Lambda表达式,代码已经变得足够简洁,让你把关注点全部都放在业务代码上。
Lambda表达式的语法
格式:(参数) -> 表达式
其中:
- 参数可以为0-n个。如果有多个参数,以逗号(,)分割。如果有一个参数,括号()可以省去;如果没有参数,括号()也不能省去。[这就有点不够纯粹了,比scala还是差了点!],参数前可以加类型名,但由于自动类型推导功能,可以省去。
- 表达式可以是一行表达式,也可以是多条语句。如果是多条语句,需要包裹在大括号{}中。
- 表达式不需要显示执行返回结果,它会从上下文中自动推导。 以下是一些例子:
一个参数
event -> System.out.println("button clicked.")
多个参数
(first, second) -> Integer.compare(first.length(), second.length()
复制代码
0个参数
() -> System.out.println("what are you nongshalei?")
表达式块
() -> {for (int i = 0; i < 100; i++) { doWork();}}
函数式接口
在Java8中新增加了一个注解: [@FunctionalInterface],函数式接口。
什么是函数式接口呢?它包含了以下特征:
- 接口中仅有一个抽象方法,但允许存在默认方法和静态方法。
- [@FunctionalInterface]注解不是必须的,但建议最好加上,这样可以通过编译器来检查接口中是否仅存在一个抽象方法。
Lambda表达式的本质就是函数式接口的匿名实现。只是把原有的接口实现方式用一种更像函数式编程的语法表示出来。
Java8的java.util.function包已经内置了大量的函数式接口,如下所示:
函数式接口 | 参数类型 | 返回类型 | 方法名 | 描述 |
Supplier | 无 | T | get | 产生一个类型为T的数据 |
Consumer | T | void | accept | 消费一个类型为T的数据 |
BiConsumer<T,U> | T,U | void | accept | 消费类型为T和类型为U的数据 |
Function<T,R> | T | R | apply | 把参数类型为T的数据经过函数处理转换成类型为R的数据 |
BiFunction<T,U,R> | T,U | R | apply | 把参数类型为T和U的数据经过函数处理转换成类型为R的数据 |
UnaryOperator | T | T | apply | 对类型T进行了一元操作,仍返回类型T |
BinaryOperator | T,T | T | apply | 对类型T进行了二元操作,仍返回类型T |
Predicate | T | void | test | 对类型T进行函数处理,返回布尔值 |
BiPredicate<T,U> | T,U | void | test | 对类型T和U进行函数处理,返回布尔值 |
从中可以看出:
- 内置的函数式接口主要分四类:Supplier, Consumer, Function,Predicate。Operator是Function的一种特例。
- 除了Supplier没有提供二元参数以外(这和java不支持多个返回值有关),其他三类都提供了二元入参。
以下是一个综合的例子:
public class FunctionalCase {
public static void main(String[] args) {
String words = "Hello, World";
String lowerWords = changeWords(words, String::toLowerCase);
System.out.println(lowerWords);
String upperWords = changeWords(words, String::toUpperCase);
System.out.println(upperWords);
int count = wordsToInt(words, String::length);
System.out.println(count);
isSatisfy(words, w -> w.contains("hello"));
String otherWords = appendWords(words, ()->{
List<String> allWords = Arrays.asList("+abc", "->efg");
return allWords.get(new Random().nextInt(2));
});
System.out.println(otherWords);
consumeWords(words, w -> System.out.println(w.split(",")[0]));
}
public static String changeWords(String words, UnaryOperator<String> func) {
return func.apply(words);
}
public static int wordsToInt(String words, Function<String, Integer> func) {
return func.apply(words);
}
public static void isSatisfy(String words, Predicate<String> func) {
if (func.test(words)) {
System.out.println("test pass");
} else {
System.out.println("test failed.");
}
}
public static String appendWords(String words, Supplier<String> func) {
return words + func.get();
}
public static void consumeWords(String words, Consumer<String> func) {
func.accept(words);
}
}
如果觉得这些内置函数式接口还不够用的话,还可以自定义自己的函数式接口,以满足更多的需求。
方法引用
如果Lambda表达式已经有实现的方法了,则可以用方法引用进行简化。 方法引用的语法如下:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
这样前面提到的Lambda表达式:
event -> System.out.println(event)
则可以替换为:
System.out::println
另一个例子:
(x,y)->x.compareToIgnoreCase(y)
可以替换为:
String::compareToIgnoreCase
注意:方法名后面是不能带参数的! 可以写成System.out::println,但不能写成System.out::println(“hello”)
如果能获取到本实例的this参数,则可以直接用this::实例方法进行访问,对于父类指定方法,用super::实例方法进行访问。
下面是一个例子:
public class Greeter {
public void greet() {
String lowcaseStr = changeWords("Hello,World", this::lowercase);
System.out.println(lowcaseStr);
}
public String lowercase(String word) {
return word.toLowerCase();
}
public String changeWords(String words, UnaryOperator<String> func) {
return func.apply(words);
}
}
class ConcurrentGreeter extends Greeter {
public void greet() {
Thread thread = new Thread(super::greet);
thread.start();
}
public static void main(String[] args) {
new ConcurrentGreeter().greet();
}
}
构造器引用
构造器引用和方法引用类似,只不过函数接口返回实例对象或者数组。 构造器引用的语法如下:
- 类::new
- 数组::new
举个例子:
List<String> labels = Arrays.asList("button1", "button2");
Stream<Button> stream = labels.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());
其中的labels.stream().map(Button::new)相当于 labels.stream().map(label->new Button(label))
再看个数组类型的构造器引用的例子:
Button[] buttons = stream.toArray(Button[]::new);
把Stream直接转成了数组类型,这里用Button[]::new来标示数组类型。
变量作用域
先看一段代码:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
System.out.println(text);
Thread.yield();
}
};
}
一个lambda表达式一般由以下三部分组成:
- 参数
- 表达式
- 自由变量
参数和表达式好理解。那自由变量是什么呢? 它就是在lambda表达式中引用的外部变量,比如上例中的text和count变量。
如果熟悉函数式编程的同学会发现,Lambda表达式其实就是”闭包”(closure)。只是Java8并未叫这个名字。 对于自由变量,如果Lambda表达式需要引用,是不允许发生修改的。
其实在Java的匿名内部类中,如果要引用外部变量,变量是需要声明为final的,虽然Lambda表达式的自由变量不用强制声明成final,但同样也是不允许修改的。
比如下面的代码:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
while (count > 0) {
count--; // 错误,不能修改外部变量的值
System.out.println(text);
}
};
}
另外,Lambda表达式中不允许声明一个和局部变量同名的参数或者局部变量。 比如下面的代码:
Path first = Paths.get("/usr/bin");
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
// 错误,变量first已经被定义
接口中的默认方法
先说说为什么要在Java8接口中新增默认方法吧。
比如Collection接口的设计人员针对集合的遍历新增加了一个forEach()方法,用它可以更简洁的遍历集合。 比如:
list.forEach(System.out::println());
但如果在接口中新增方法,按照传统的方法,Collection接口的自定义实现类都要实现forEach()方法,这对广大已有实现来说是无法接受的。
于是Java8的设计人员就想出了这个办法:在接口中新增加一个方法类型,叫默认方法,可以提供默认的方法实现,这样实现类如果不实现方法的话,可以默认使用默认方法中的实现。
一个使用例子:
public interface Person {
long getId();
default String getName() {
return "jack";
}
}
默认方法的加入,可以替代之前经典的接口和抽象类的设计方式,统一把抽象方法和默认实现都放在一个接口中定义。这估计也是从Scala的Trait偷师来的技能吧。
接口中的静态方法
除了默认方法,Java8还支持在接口中定义静态方法以及实现。
比如Java8之前,对于Path接口,一般都会定义一个Paths的工具类,通过静态方法实现接口的辅助方法。
接口中有了静态方法就好办了, 统一在一个接口中搞定!虽然这看上去破坏了接口原有的设计思想。
public interface Path{
public static Path get(String first, String... more) {
return FileSystem.getDefault().getPath(first, more);
}
}
这样Paths类就没什么意义了~
小结
使用Lambda表达式后可以大幅减少冗余的模板式代码,使把更多注意力放在业务逻辑上,而不是复制一堆重复代码, 除非你在一个用代码行数来衡量工作量的公司,你觉得呢?
相关推荐
- 甲骨文签署多项大型云协议,其一未来可贡献超300亿美元年收入
-
IT之家7月1日消息,根据甲骨文Oracle当地时间6月30日向美国证券交易委员会(SEC)递交的FORM8-K文件,该企业在始于2025年6月1日的202...
- 甲骨文获TEMU巨额合同,后者大部分基础设施将迁移至Oracle云
-
IT之家6月23日消息,Oracle甲骨文创始人、董事长兼首席技术官LarryEllison(拉里埃里森)在本月早些时候的2025财年第四财季和全财年财报电话会议上表示,Oracle...
- Spring Boot 自定义数据源设置,这些坑你踩过吗?
-
你在使用SpringBoot进行后端开发的过程中,是不是也遇到过这样的问题:项目上线后,数据库连接总是不稳定,偶尔还会出现数据读取缓慢的情况,严重影响了用户体验。经过排查,发现很大一部分原因竟然...
- 一个开箱即用的代码生成器(一个开箱即用的代码生成器是什么)
-
今天给大家推荐一个好用的代码生成器,名为renren-generator,该项目附带前端页面,可以很方便的选择我们所需要生成代码的表。首先我们通过git工具克隆下来代码(地址见文末),导入idea。...
- 低代码建模平台-数据挖掘平台(低代码平台的实现方式)
-
现在来看一下数据连接。·这里是管理数据连接的空间,点击这里可以新增一个数据连接。·输入连接名称,然后输入url,是通过gdbc的方式去连接的数据库,目前是支持mysql、oracle以及国产数据库达梦...
- navicat 17.2.7连接oracle数据库提示加载oracle库失败
-
系统:macOS15.5navicat版本:navicatpremiumlite17.2.7连接oracle测试报错:加载oracle库失败【解决办法】:放达里面找到程序,显示简介里面勾选“使...
- 开源“Windows”ReactOS更新:支持全屏应用
-
IT之家6月17日消息,ReactOS团队昨日(6月16日)在X平台发布系列推文,公布了该系统的最新进展,包括升级Explorer组件,支持全屏应用,从Wine项目引入了...
- SSL 推出采用全模拟内置混音技术的模拟调音台Oracle
-
英国调音台传奇品牌SolidStateLogic宣布推出Oracle——一款采用全模拟内置混音技术的调音台,在紧凑的AWS尺寸机箱内集成了大型调音台的功能。该调音台提供24输入和...
- 47道网络工程师常见面试题,看看有没有你不会的!
-
你们好,我的网工朋友。网络工程师面试的时候,都会被问到什么?这个问题其实很泛,一般来说,你肯定要先看明白岗位需求写的是什么。基本上都是围绕公司需要的业务去问的。但不可否认的是,那些最基础的概念,多少也...
- 汉得信息:发布EBS系统安装启用JWS的高效解决方案
-
e公司讯,从汉得信息获悉,近日,微软官方宣布InternetExplorer桌面应用程序将于2022年6月15日正式停用。目前大部分客户都是使用IE浏览器打开EBS的Form界面,IE停用后,只能使...
- 36.9K star ! 推荐一个酷炫低代码开发平台!功能太强!
-
前言最近在逛github,看看能不能搜罗到一些对自己有帮助的开源软件。不经意间看到一个高star的java开源项目:jeecg-boot。进入在线演示版一看,感叹实在是太牛了!此开源项目不管是给来学习...
- Linux新手入门系列:Linux下jdk安装配置
-
本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...
- 手把手教你在嵌入式设备中使用SQLite3
-
摘要:数据库是用来存储和管理数据的专用软件,使得管理数据更加安全,方便和高效。数据库对数据的管理的基本单位是表(table),在嵌入式linux中有时候它也需要用到数据库,听起来好难,其实就是几个函数...
- JAVA语言基础(java语言基础知识)
-
一、计算机的基本概念什么是计算机?计算机(Computer)全称:电子计算机,俗称电脑。是一种能够按照程序运行、自动高速处理海量数据的现代化智能电子设备。由硬件和软件组成、没有安装过任何软件的计算机称...
- 再见 Navicat!一款开源的 Web 数据库管理工具!
-
大家好,我是Java陈序员。在日常的开发工作中,常常需要与各种数据库打交道。而为了提高工作效率,常常会使用一些可视化工具进行操作数据库。今天,给大家介绍一款开源的数据库管理工具,无需下载安装软件,基...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 甲骨文签署多项大型云协议,其一未来可贡献超300亿美元年收入
- 甲骨文获TEMU巨额合同,后者大部分基础设施将迁移至Oracle云
- Spring Boot 自定义数据源设置,这些坑你踩过吗?
- 一个开箱即用的代码生成器(一个开箱即用的代码生成器是什么)
- 低代码建模平台-数据挖掘平台(低代码平台的实现方式)
- navicat 17.2.7连接oracle数据库提示加载oracle库失败
- 开源“Windows”ReactOS更新:支持全屏应用
- SSL 推出采用全模拟内置混音技术的模拟调音台Oracle
- 47道网络工程师常见面试题,看看有没有你不会的!
- 汉得信息:发布EBS系统安装启用JWS的高效解决方案
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)