前言
Lambda
表达式是JDK8
的一个新特性,可以取代大部分的匿名内部类,写出更优雅的Java
代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构
。
JDK
也提供了大量的内置函数式接口
供我们使用,使得Lambda
表达式的运用更加方便、高效
。
一、什么是Lambda表达式
Lambda
表达式,也称为闭包
:java8
的新特性,lambda运行将函数作为一个方法的参数,也就是将函数作为参数传递到方法中
。使用lambda表达式可以让代码更加简洁。
Lambda
表达式常用于简化接口实现,关于接口实现,可以有很多种方式。例如:
- 创建接口的实现类;
- 使用匿名内部类;
但是lambda
表达式,比这两种方式都简单。代码示例如下:
interfaceTestInterface{ publicvoidtestFun(); } publicclassTestClass { publicstaticvoidmain(String[] args) { //使用lambda表达式实现接口TestClasstest= () -> { System.out.println("test"); }; test.testFun(); } }
二、使用前提
上文中提到,lambda表达式
可以在⼀定程度上简化
接口的实现。但是,并不是所有的接口
都可以使用lambda
表达式来简化接口的实现的。
先说结论,lambda表达式,只能实现函数式接口
。lambda
表达式毕竟只是⼀个匿名方法
。
三、函数式接口
3.1 概念
函数式接口
在 Java 中是指: 有且仅有一个抽象方法的接口 。
函数式接口
,即适用于函数式编程场景的接口
。而 Java 中的函数式编程体现就是Lambda
,所以函数式接口就是可以适用于Lambda
使用的接口。只有确保接口中有且仅有一个抽象方法,Java
中的 Lambda
才能顺利地进行推导。
备注:
语法糖
是指使用更加方便,但是原理不变的代码语法。例如:
在遍历集合时使用的
for-each
语法,其实底层的实现原理仍然是迭代器
,这便是语法糖
。从应用层面来讲, Java 中的Lambda
可以被当做是匿名内部类的语法糖
,但是二者在原理上是不同的。
3.2 格式
只要确保接口中有且仅有一个抽象方法
即可,伪代码如下:
修饰符interface接口名称 { publicabstract返回值类型方法名称(可选参数信息); // 其它非抽象方法内容}
由于接口当中抽象方法的public abstract
是可以省略的,所以定义一个函数式接口很简单:
publicinterfaceTestFunctionalInterface { voidtestMethod(); }
3.3 @FunctionalInterface
与 @Override
注解的作用类似, Java 8
中专门为函数式接口引入了一个新的注解:@FunctionalInterface
。该注解可用于一个接口的定义上:
publicinterfaceTestFunctionalInterface { voidtestMethod(); }
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
3.4 自定义函数式接口
对于刚刚定义好的 MyFunctionalInterface
函数式接口,典型使用场景就是作为方法的参数:
publicclassTestFunctionalClass { // 使用自定义的函数式接口作为方法参数privatestaticvoiddoSomeThing(TestFunctionalInterfacetestInterFace){ testInterFace.testMethod(); } publicstaticvoidmain(String[] args) { // 调用函数式接口的方法doSomeThing(() ->System.out.println("Hello world!")); } }
四 语法格式
4.1 基础语法
lambda
表达式,其实本质来讲,就是⼀个匿名函数
。因此在写lambda
表达式的时候,不需要关心方法名是什么。
实际上,我们在写lambda
表达式的时候,也不需要关心返回值类型,只需要关注两部分内容即可:参数列表和方法体
(参数1,参数2,…) -> {
方法体
};
各部分详述
:
参数部分
:方法的参数列表,要求和实现的接口中的方法参数部分⼀致,包括参数的数量和类型。
方法体部分
: 方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
->
: 分隔参数部分和方法体部分。
总结来说
:语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
4.2 重要特征
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
代码示例:
publicclassDemo01 { /**多参数无返回*/publicinterfaceNoReturnMultiParam { voidmethod(inta, intb); } /**无参无返回值*/publicinterfaceNoReturnNoParam { voidmethod(); } /**一个参数无返回*/publicinterfaceNoReturnOneParam { voidmethod(inta); } /**多个参数有返回值*/publicinterfaceReturnMultiParam { intmethod(inta, intb); } /*** 无参有返回*/publicinterfaceReturnNoParam { intmethod(); } /**一个参数有返回值*/publicinterfaceReturnOneParam { intmethod(inta); } publicstaticvoidmain(String[] args) { //无参无返回NoReturnNoParamnoReturnNoParam= () -> { System.out.println("NoReturnNoParam"); }; noReturnNoParam.method(); //一个参数无返回NoReturnOneParamnoReturnOneParam= (inta) -> { System.out.println("NoReturnOneParam param:"+a); }; noReturnOneParam.method(6); //多个参数无返回NoReturnMultiParamnoReturnMultiParam= (inta, intb) -> { System.out.println("NoReturnMultiParam param:"+"{"+a+","++b+"}"); }; noReturnMultiParam.method(6, 8); //无参有返回值ReturnNoParamreturnNoParam= () -> { System.out.print("ReturnNoParam"); return1; }; intres=returnNoParam.method(); System.out.println("return:"+res); //一个参数有返回值ReturnOneParamreturnOneParam= (inta) -> { System.out.println("ReturnOneParam param:"+a); return1; }; intres2=returnOneParam.method(6); System.out.println("return:"+res2); //多个参数有返回值ReturnMultiParamreturnMultiParam= (inta, intb) -> { System.out.println("ReturnMultiParam param:"+"{"+a+","+b+"}"); return1; }; intres3=returnMultiParam.method(6, 8); System.out.println("return:"+res3); } }
控制台输出:
NoReturnNoParamNoReturnOneParamparam:6NoReturnMultiParamparam:{6,8} ReturnNoParamreturn:1ReturnOneParamparam:6return:1ReturnMultiParamparam:{6,8} return:1
五 语法简化
5.1 参数部分的精简
5.1.1 参数的类型
由于在接口的方法中,已经定义了每⼀个参数的类型是什么。而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类 型需要和接口中的方法保持⼀致。因此,此时lambda表达式中的参数的类型可以省略不写。
注意事项:
如果需要省略参数的类型,要保证:要省略, 每⼀个参数的类型都必须省略不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。
// 有参+返回值Testtest= (name,age) -> { System.out.println(name+age+"了!"); returnage+1; }; intage=test.test("小刘学编程",18); System.out.println(age);
5.1.2 参数的小括号
如果方法的参数列表中的参数数量 有且只有⼀个,此时,参数列表的小括号是可以省略不写的。
注意事项:
- 只有当参数的数量是⼀个的时候, 多了、少了都不能省略。
- 省略掉小括号的同时, 必须要省略参数的类型
//一个参数Testtest=name-> { System.out.println(name+"test"); }; test.test("小刘学编程");
5.2 方法体部分的精简
当⼀个方法体中的逻辑,有且只有⼀句的情况下,⼤括号可以省略
Testtest=name->System.out.println(name+"test");
test.test("小新");
5.3 return部分的精简
如果⼀个方法中唯⼀的⼀条语句是⼀个返回语句, 此时在省略掉大括号的同时, 也必须省略掉return。
Testtest= (a,b) ->a+b;
六、常用示例
lambda
表达式是为了简化接口的实现的,在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中需要处理的逻辑比较复杂,会对程序的可读性造成非常大的影响,⼀般情况会单独的写⼀个方法。在lambda表达式中直接引用这个方法即可。
函数引用:引用⼀个已经存在的方法,使其替代lambda表达式
完成接口的实现
6.1 静态方法的引用
语法:类::静态方法
注意事项:
- 在引用的方法后面,不要添加小括号。
- 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的⼀致
classSubtraction{ publicstaticintsubtract(inta,intb ){ // 稍微复杂的逻辑:计算a和b的差值的绝对值if (a>b) { returna-b; } returnb-a; } } interfaceTestInterface{ inttest(inta,intb); } publicclassTestClass { publicstaticvoidmain(String[] args) { //实现多个参数,一个返回值的接口//对一个静态方法的引用,语法:类::静态方法TestInterfacetest=Subtraction::subtract; System.out.println(test.test(1,2)); } }
6.2 非静态方法的引用
语法:方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象
注意事项:
- 在引用的方法后⾯,不要添加小括号。
- 引用的这个方法, 参数(数量、类型) 和 返回值, 必须要跟接口中定义的⼀致。
publicclassTest06 { publicstaticvoidmain(String[] args) { //对非静态方法的引用,需要使用对象来完成Test2test2=newCalculator()::calculate; System.out.println(test2.calculate(2, 3)); } privatestaticclassCalculator{ publicintcalculate(inta, intb) { returna>b?a-b : b-a; } } } interfaceTest2{ intcalculate(inta,intb); }
6.3 构造方法的引用
使用场景
如果某⼀个函数式接口中定义的方法,仅仅是为了得到⼀个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
语法:类名::new
注意事项:可以通过接口中的方法的参数, 区分引用不同的构造方法。
interfaceItemCreatorBlankConstruct { ItemgetItem(); } interfaceItemCreatorParamContruct { ItemgetItem(intid, Stringname, doubleprice); } publicclassExe2 { publicstaticvoidmain(String[] args) { ItemCreatorBlankConstructcreator= () ->newItem(); Itemitem=creator.getItem(); ItemCreatorBlankConstructcreator2=Item::new; Itemitem2=creator2.getItem(); ItemCreatorParamContructcreator3=Item::new; Itemitem3=creator3.getItem(112, "小刘学编程", 135.99); } }
6.4 Lambda 表达式创建线程
创建线程一般都是通过创建Thread
对象,然后通过匿名内部类重写run()
方法,一提到匿名内部类就应该想到可以使用 lambda 表达式来简化线程的创建过程。
Threadt=newThread(() -> { for (inti=0; i<10; i++) { System.out.println("新建线程->"+":"+i); } }); t.start();
6.5 遍历集合
可以调用集合的 public void forEach(Consumer<? super E> action)
方法,通过 lambda 表达式的方式遍历集合中的元素。以下是Consumer接口
的方法以及遍历集合的操作。Consumer
接口是jdk
提供的一个函数式接口。
publicinterfaceConsumer<T> { voidaccept(Tt); //....} ArrayList<Integer>list=newArrayList<>(); Collections.addAll(list, 1,2,3,4,5); //lambda表达式 方法引用list.forEach(System.out::println); list.forEach(item-> { if (element%2==0) { System.out.println(item); } });
6.6 删除集合中的某个元素
通过public boolean removeIf(Predicate<? super E> filter)
方法来删除集合中的某个元素,Predicate
也是jdk
为提供的一个函数式接口,可以简化程序的编写。
ArrayList<Item>items=newArrayList<>(); items.add(newItem(11, "小牙刷", 12.05 )); items.add(newItem(5, "日本马桶盖", 999.05 )); items.add(newItem(7, "格力空调", 888.88 )); items.add(newItem(17, "肥皂", 2.00 )); items.add(newItem(9, "冰箱", 4200.00 )); items.removeIf(ele->ele.getId() ==7); //通过 foreach 遍历,查看是否已经删除items.forEach(System.out::println);
6.7 集合内元素的排序
若要为集合内的元素排序,就必须调用sort
方法,传入比较器匿名内部类重写compare 方法
,现在可以使用lambda 表达式
来简化代码。
ArrayList<Item>list=newArrayList<>(); list.add(newItem(13, "背心", 7.80)); list.add(newItem(11, "半袖", 37.80)); list.add(newItem(14, "风衣", 139.80)); list.add(newItem(12, "秋裤", 55.33)); list.sort((o1, o2) ->o1.getId() -o2.getId()); System.out.println(list);
七、注意
这⾥类似于局部内部类
、匿名内部类
,依然存在闭包的问题。如果在lambda表达式
中,使用到了局部变量
,那么这个局部变量会被隐式的声明为 final
。是⼀个常量,不能修改值。
如下代码示例:如果我们把注释放开会报错,提示num
值是final
不能被改变。这里虽然没有标识num
类型为final
,但是在编译期间虚拟机会加上fina
修饰关键字。
publicstaticvoidmain(String[] args) { intnum=10; Test<String>test= () -> { System.out.println(num); }; //num = num + 2;test.doSomeThing("hello world !"); }
总结
本文从Lambda表达式的基础概念、函数式接口、以及Lambda表达式的常用示例几方面完整的讨论了这一Java8新增的特性,实际开发中确实为我们提供了许多便利,简化了代码。欢迎小伙伴继续提出不同的见解一起讨论!
参考 & 鸣谢
1、Java中Lambda表达式使用及详解 https://blog.csdn.net/qq_45263520/article/details/123772771
2、Lambda表达式详解 https://www.cnblogs.com/haixiang/p/11029639.html
感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!