一、Lambda 表达式简介
1、什么是 Lambda 表达式
Lambda
表达式是在 JDK 8 中引入的一个新特性,可用于取代大部分的匿名内部类。使用 Lambda
表达式可以完成用少量的代码实现复杂的功能,极大的简化代码代码量和代码结构。同时,JDK 中也增加了大量的内置函数式接口供我们使用,使得在使用 Lambda 表达式时更加简单、高效。
2、为什么需要 Lambda 表达式
谈起为什么需要 Lambda
表达式,那得从函数式编程开始说起。函数式编程
可以简单说是一种编程规范
,也就是如何编写程序的方法论。它属于结构化编程
的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。函数式编程有很多优点,其中包括:
- 易于并发编程;
- 代码的热升级;
- 更方便的代码管理;
- 代码简洁,开发快速;
- 接近自然语言,易于理解;
函数式编程在 C#
、Python
、JavaScript
中都得到充分体现,在 Java 8 版本中也得到了支持。最明显的就是对 Lambda
表达式的支持。很多种迹象表明未来编程语言将是逐渐融合各自的特性,而不是单纯的声明式语言函数编程语言。将来声明式编程语言借鉴函数编程思想,函数编程语言融合声明式编程特性,这几乎是一种必然趋势。
在 Java 中主要引入 Lambda 表达式的作用是对现有编码语义的优化,减少语法冗余。轻量级的将代码封装为数据,使代码简洁,易于理解。
二、函数式接口和定义
1、什么是函数式接口
函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法
的接口。Java 中函数式接口被隐式转换为 Lambda 表达式,只有保证接口类中有且只有一个抽象方法,Java 中的 Lambda 表达式才能对该方法进行推导。
2、函数式接口格式
在 Java 函数式接口类中,需要满足接口类中只能有一个抽象方法。总结如下:
- 接口有且仅有一个抽象方法;
- 允许定义静态方法;
- 允许定义默认方法;
- 允许 java.lang.Object 中的 public 方法;
在创建函数式接口时,可以在接口类上面加上@FunctionalInterface
注解,这时编译器就可以对接口结构
进行强制检查
是否符合函数式接口规则,如果不符合规则
就显示错误。当然,这个注解只是用于检查,即使不加上该注解,只要符合函数式接口规则
一样也是函数式接口。
下面创建个演示的函数式接口,如下:
1 | // @FunctionalInterface 注解说明: |
按照上面函数式接口,定义一个示例的函数式接口类,代码如下:
1 |
|
3、函数式接口和 Lambda 表达式的关系
函数式接口和 Lambda 表达式的关系可以总结为:
- 函数式接口只包含一个操作方法;
- Lambda 表达式只能操作一个方法;
- Java 中的 Lambda 表达式核心就是一个函数式编程接口的实现。
4、当前 JDK 8 中存在的函数式接口类
在 JDK 1.8 之前,已经存在部分函数式接口,如下:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
在 JDK 8 中新增了函数接口 java.util.function 包,里面包含了很多用来支持 Java 的函数式编程的接口类,如下:
类名称 | 描述信息 |
---|---|
BiConsumer<T,U> | 代表了一个接受两个输入参数的操作,并且不返回任何结果。 |
BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果。 |
BinaryOperator |
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。 |
BiPredicate<T,U> | 代表了一个两个参数的 boolean 值方法。 |
BooleanSupplier | 代表了 boolean 值结果的提供方。 |
Consumer |
代表了接受一个输入参数并且无返回的操作。 |
DoubleBinaryOperator | 代表了作用于两个 double 值操作符的操作,并且返回了一个 double 值的结果。 |
DoubleConsumer | 代表一个接受 double 值参数的操作,并且不返回结果。 |
DoubleFunction |
代表接受一个 double 值参数的方法,并且返回结果。 |
DoublePredicate | 代表一个拥有 double 值参数的 boolean 值方法。 |
DoubleSupplier | 代表一个 double 值结构的提供方。 |
DoubleToIntFunction | 接受一个 double 类型输入,返回一个 int 类型结果。 |
DoubleToLongFunction | 接受一个 double 类型输入,返回一个 long 类型结果。 |
DoubleUnaryOperator | 接受一个参数同为类型 double,返回值类型也为 double。 |
Function<T,R> | 接受一个输入参数,返回一个结果。 |
IntBinaryOperator | 接受两个参数同为类型 int,返回值类型也为 int。 |
IntConsumer | 接受一个 int 类型的输入参数,无返回值。 |
IntFunction |
接受一个 int 类型输入参数,返回一个结果。 |
IntPredicate | 接受一个 int 输入参数,返回一个布尔值的结果。 |
IntSupplier | 无参数,返回一个 int 类型结果。 |
IntToDoubleFunction | 接受一个 int 类型输入,返回一个double类型结果。 |
IntToLongFunction | 接受一个 int 类型输入,返回一个 long 类型结果。 |
IntUnaryOperator | 接受一个参数同为类型 int,返回值类型也为 int。 |
LongBinaryOperator | 接受两个参数同为类型 long,返回值类型也为 long。 |
LongConsumer | 接受一个 long 类型的输入参数,无返回值。 |
LongFunction |
接受一个 long 类型输入参数,返回一个结果。 |
LongPredicate | R接受一个 long 输入参数,返回一个布尔值类型结果。 |
LongSupplier | 无参数,返回一个结果 long 类型的值。 |
LongToDoubleFunction | 接受一个 long 类型输入,返回一个 double 类型结果。 |
LongToIntFunction | 接受一个 long 类型输入,返回一个 int 类型结果。 |
LongUnaryOperator | 接受一个参数同为类型 long,返回值类型也为 long。 |
ObjDoubleConsumer |
接受一个 object 类型和一个 double 类型的输入参数,无返回值。 |
ObjIntConsumer |
接受一个 object 类型和一个 int 类型的输入参数,无返回值。 |
ObjLongConsumer |
接受一个 object 类型和一个 long 类型的输入参数,无返回值。 |
Predicate |
接受一个输入参数,返回一个布尔值结果。 |
Supplier |
无参数,返回一个结果。 |
ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个 double 类型结果 |
ToDoubleFunction |
接受一个输入参数,返回一个 double 类型结果。 |
ToIntBiFunction<T,U> | 接受两个输入参数,返回一个 int 类型结果。 |
ToIntFunction |
接受一个输入参数,返回一个 int 类型结果。 |
ToLongBiFunction<T,U> | 接受两个输入参数,返回一个 long 类型结果。 |
ToLongFunction |
接受一个输入参数,返回一个 long 类型结果。 |
UnaryOperator |
接受一个参数为类型 T,返回值类型也为 T。 |
5、JDK 中常见的函数式接口类
上面 java.util.function 包提供了众多的函数式接口,其中常用的有:
- java.util.function.Predicate
:接收参数对象 T,返回一个 boolean 类型结果。 - java.util.function.Comsumer
:接收参数对象 T,不返回结果。 - java.util.function.Function<T,R>:接收参数对象 T,返回结果对象 R。
- java.util.function.Supplier
:不接收参数,提供 T 对象的创建工厂。 - java.util.function.UnaryOperator
:接收参数对象 T,返回结果对象 T。 - java.util.function.BinaryOperator
:接收两个 T 对象,返回一个 T 对象结果。
注:为了使易懂,下面示例中 Lambda 表达式没有使用的最简易写法,而是使用比较繁琐的写法。
(1)、java.util.function.Predicate
- 接口类作用: 接收参数对象T,返回一个 boolean 类型结果。
- 接口类源码:
1 |
|
使用示例:
1 | public class PredicateExample { |
日常开发中,需要对某个值进行判断操作,并且返回判断结果,这时可以考虑使用 Predicate 接口,以及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(2)、java.util.function.Comsumer
- 接口类作用: 接收参数对象 T,不返回结果。
- 接口类源码:
1 |
|
使用示例:
1 | /** 这里创建一个 Consumer,模拟发送消息并打印内容 */ |
日常开发中,需要对某个类型进行公共处理,并且不需要任何返回值,这时可以考虑使用 Consumer 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(3)、java.util.function.Function<T,R>
- 接口类作用: 接收参数对象 T,返回结果对象 R。
- 接口类源码:
1 |
|
使用示例:
1 | /** 这里创建一个 Function,对传入的参数进行验证,如果包含 a 字符就返回1,否则返回0 */ |
日常开发中,需要对某个类型数据进行操作,经过一系列逻辑后转换为一个新的类型进行返回,这时可以考虑使用 Function 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(4)、java.util.function.Supplier
- 接口类作用: 不接收参数,提供 T 对象的创建工厂。
- 接口类源码:
1 |
|
使用示例:
1 | /** 这里创建一个 Supplier,用于生成随机ID,通过 get 方法获取生成的随机ID值 */ |
日常开发中,需要创建一个统一的工厂用于生成特定的产物完成特定的目标,这时就可以考虑使用 Supplier 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(5)、java.util.function.UnaryOperator
- 接口类作用: 接收参数对象 T,返回结果对象 T。
- 接口类源码:
1 | // 可以看到 UnaryOperator 继承了 Function 接口 |
使用示例:
1 | /** 这里创建一个 UnaryOperator,接收一个字符串进行加工处理后返回新字符串 */ |
日常开发中,我们经常要对一个已有的对象进行操作修改,然后返回修改后的对象,这时就可以考虑使用 UnaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(6)、java.util.function.BinaryOperator
- 接口类作用: 接收两个 T 对象,返回一个 T 对象结果。
- 接口类源码:
1 |
|
使用示例:
1 | /** 这里创建一个 BinaryOperator,比较传入的两个参数哪个值最大,返回最大值 */ |
在使用这几种基本函数接口时传入参数 T 不能是基本类型,如 BinaryOperator 中 T 不能设置为 int,只能使用 Integer 包装类,这也限制了 Lambda 表达式中设置参数时候,使用包装类替换基本类型。
日常开发中,我们有时候会对两个对象进行操作,执行一些操作逻辑后返回结果,这时就可以考虑使用 BinaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
三、Lambda 表达式基本语法
1、Lambda 表达式的组成
Lambda 表达式的组成可以拆分为:
声明: 与 Lambda 表达式绑定的接口类型。
参数: 参数包含在一对
()
中,和绑定的接口中的抽象方法中的参数个数及顺序一致。操作符:
->
执行代码块: 执行代码块包含在一对
{}
中,出现在操作符的右侧。[接口声明] = (参数) -> {执行代码块}
2、Lambda 表达式的格式
Lambda 表达式可以分为下面几种格式:
- 无参数,无返回值;
- 有一个参数,无返回值;
- 左侧只有一个参数,小括号可以省略不写;
- 有两个以上参数,有返回值,并且Lambda 体中有多条语句;
- 若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写;
- Lambda 表达式的参数列表的数据类型可以省略不写,jvm编译器会进行上下文推断出,数据类型“类型推断”;
(1)、无参数,无返回值
1 | () -> System.out.println("测试"); |
(2)、有一个参数,无返回值
1 | (x) -> System.out.println(x); |
(3)、左侧只有一个参数,小括号可以省略不写
1 | x -> System.out.println(x); |
(4)、有两个以上参数,有返回值,并且Lambda 体中有多条语句
1 | Comparator<Integer> comparator = (x, y) -> { |
(5)、若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写
1 | Comparator<Integer> Comparator = (x, y) -> Integer.compare(x, y); |
(6)、Lambda 表达式的参数列表的数据类型可以省略不写,JVM 在运行时,会自动根据绑定的抽象方法中的参数,进行数据类型推导
1 | (Integer x, Integer y) -> Integer.compare(); |
四、Lambda 表达式中变量作用域
Java 中的变量捕获与变量隐藏:
- 变量捕获: 局部类和匿名内部类可以访问被 final 修饰的封闭块内的局部变量。
- 变量隐藏: 在一个类中,子类中的成员变量如果和父类中的成员变量同名,那么即使他们类型不一样,只要名字一样,父类中的成员变量都会被隐藏。
在局部类和匿名内部类都存在 变量捕获
与 变量隐藏
,而在 Lambda
表达式中则只支持 变量捕获
。
下面是对这作用域得演示示例:
(1)、匿名内部类:
1 | public class VariableExample { |
(2)、Lambda 表达式:
1 | public class VariableExample { |
五、Lambda 表达式方法重载问题
当使用 Lambda 表达式,调用一个类中的重载方法,且方法中的参数为都为函数接口,函数接口中定义的方法接收的参数类型相同,这时候 Lambda 是无法推断出要调用哪个方法。
函数接口A:
1 |
|
函数接口B:
1 |
|
示例,实现方法重载与测试的 Main 方法:
1 | public class LambdaOverloadExample { |
上面注掉的那部分代码,在编辑器中直接提示错误
,很显然 Lambda
表达式无法直接推断
出使用哪个类中的重载方法
。其实,只要明确告诉 Lambda
表达式使用哪个参数
,就可以很简单的解决问题,比如以上面的例子,在 Lambda
表达式使用 method
方法时,将参数类型
转换为对应的要使用的类型
就可以解决这个问题,代码如下:
1 | // 转换参数为 MyInterfaceA |
按上面进行修改后就可以正常使用 Lambda 表达式了,如果不习惯也可以使用匿名内部类进行方法调用,内名内部类是没有相关问题的。
六、Lambda 表达式方法引用
方法引用本质上就是对方法调用的简化,方法引用和函数式接口绑定,在使用过程中会创建函数式接口的实例,是结合 Lambda 表达式的一种特性。在应用过程中,方法引用常分为:
- 静态方法引用
- 实例方法引用
- 构造方法引用
- 特定类型的任意对象实例方法引用
注意:在使用 Lmabda 方法引用时虽然能够简化代码,但是在实际开发中不可因需要简化代码而过度使用方法引用,因为他会在很大程度上降低代码可读性。
1、创建示例的实体类
为了下面示例方便,我们首先创建一个 Person 实体类,如下:
1 | public class Person { |
2、静态方法引用示例
静态方法的引用的使用: 静态方法所在类
.方法名称()
–> 静态方法所在类
::
方法名称
创建一个使用静态方法引用的示例类:
1 | public class StaticMethodExample { |
3、实例方法引用示例
实例方法的引用的使用:创建类型对应一个对象
–> 对应应用
::
实例方法名称
创建一个封装实例方法的类:
1 | public class PersonUtil{ |
创建一个使用实例方法引用的示例类:
1 | public class InstanceMethodExample { |
4、构造方法引用示例
构造方法的引用的使用:绑定函数式接口
创建一个函数式接口,且设置接收参数和 Person 的构造方法相同,返回 Person 对象的方法定义:
1 |
|
创建一个使用构造方法引用的示例类
1 | public class LambdaConstructorMethodExample { |
5、特定类型的任意对象实例方法引用示例
特定类型的任意对象实例方法引用示例:特定类型
::
特定类型的方法
以下是对特定类型的任意对象的实例方法的引用的示例:
1 | public class LambdaExample { |
这里根据定义的集合 strList
去推导目标类型参数值,如果不符合
后面传入的方法引用所对应的类型,将报错。该方法参考等效 Lambda
表达式 String::compareToIgnoreCase
的参数列表 (String a, String b)
,其中 a 和 b 是用于更好地描述这个例子中的任意名称。方法引用将调用该方法 a.compareToIgnoreCase(b)
。