🔎这里是【Java】,关注我学习Java不迷路
👍如果对你有帮助,给博主一个免费的点赞以示鼓励
欢迎各位🔎点赞👍评论收藏⭐️
👀专栏介绍
【Java】 目前主要更新Java,一起学习一起进步。
👀本期介绍
本期主要介绍函数式接口
文章目录
第一章 函数式接口
1.1 概念
1.2 格式
1.3 @FunctionalInterface注解
1.4 自定义函数式接口
第二章 函数式编程
2.1 Lambda的延迟执行
2.2 使用Lambda作为参数和返回值
第三章 常用函数式接口
3.1 Supplier接口
3.2 练习:求数组元素最大值
3.3 Consumer接口
3.4 练习:格式化打印信息
3.5 Predicate接口
3.6 练习:集合信息筛选
3.7 Function接口
3.8 练习:自定义函数模型拼接
第一章 函数式接口
1.1 概念
函数式接口在
Java
中是指:
有且仅有一个抽象方法的接口
。
函数式接口,即适用于函数式编程场景的接口。而
Java
中的函数式编程体现就是
Lambda
,所以函数式接口就是可
以适用于
Lambda
使用的接口。只有确保接口中有且仅有一个抽象方法,
Java
中的
Lambda
才能顺利地进行推导。
备注:
“
语法糖
”
是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的
for-each
语法,其实
底层的实现原理仍然是迭代器,这便是
“
语法糖
”
。从应用层面来讲,
Java
中的
Lambda
可以被当做是匿名内部
类的
“
语法糖
”
,但是二者在原理上是不同的。
1.2 格式
只要确保接口中有且仅有一个抽象方法即可:
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:
1.3 @FunctionalInterface注解
与
@Override
注解的作用类似,
Java 8
中专门为函数式接口引入了一个新的注解:
@FunctionalInterface
。该注
解可用于一个接口的定义上:
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将
会报错。需要
注
意
的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都
一样。
1.4 自定义函数式接口
对于刚刚定义好的
MyFunctionalInterface
函数式接口,典型使用场景就是作为方法的参数:
第二章 函数式编程
在兼顾面向对象特性的基础上,
Java
语言通过
Lambda
表达式与方法引用等,为开发者打开了函数
式编程的大门。
下面我们做一个初探。
2.1 Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而
Lambda
表达式是延迟执行
的,这正好可以
作为解决方案,提升性能。
性能浪费的日志案例
注
:
日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进
行打印输出:
这段代码存在问题:无论级别是否满足要求,作为
log
方法的第二个参数,三个字符串一定会首先
被拼接并传入方
法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能
浪费。
备注:
SLF4J
是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推
荐首先进行
字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况
下才会进
行字符串拼接。例如:
LOGGER.debug("
变量
{}
的取值为
{}
。
", "os", "macOS")
,其中的大括号
{}
为占位
符。如果满足日志级别要求,则会将
“os”
和
“macOS”
两个字符串依次拼接到大括号的位置;否则不
会进行字
符串拼接。这也是一种可行解决方案,但
Lambda
可以做到更好。
体验
Lambda
的更优写法
使用
Lambda
必然需要一个函数式接口:
然后对 log 方法进行改造:
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行
拼接。
证明
Lambda
的延迟
下面的代码可以通过结果进行验证:
从结果中可以看出,在不符合级别要求的情况下,
Lambda
将不会执行。从而达到节省性能的效
果。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过
调用方法
来完成。而是否调用其所在方法是在条件判断之后才执行的。
2.2 使用Lambda作为参数和返回值
如果抛开实现原理不说,
Java
中的
Lambda
表达式可以被当作是匿名内部类的替代品。如果方法的
参数是一个函数
式接口类型,那么就可以使用
Lambda
表达式进行替代。使用
Lambda
表达式作为方法参数,其实
就是使用函数式
接口作为方法参数。
例如
java.lang.Runnable
接口就是一个函数式接口,假设有一个
startThread
方法使用该接口作为
参数,那么就
可以使用
Lambda
进行传参。这种情况其实和
Thread
类的构造方法参数为
Runnable
没有本质区
别。
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个
Lambda
表达
式。当需要通过一
个方法来获取一个
java.util.Comparator
接口类型的对象作为排序器时
,
就可以调该方法获取。
其中直接return一个Lambda表达式即可。
第三章 常用函数式接口
JDK
提供了大量常用的函数式接口以丰富
Lambda
的典型使用场景,它们主要在
java.util.function
包中被提供。
下面是最简单的几个接口及使用示例。
3.1 Supplier接口
java.util.function.Supplier
接口仅包含一个无参的方法:
T get()
。用来获取一个泛型参数指定
类型的对
象数据。由于这是一个函数式接口,这也就意味着对应的
Lambda
表达式需要
“
对外提供
”
一个符合
泛型类型的对象
数据。
3.2 练习:求数组元素最大值
题目
使用
Supplier
接口作为方法参数类型,通过
Lambda
表达式求出
int
数组中的最大值。提示:接口的
泛型请使用
java.lang.Integer
类。
解答
3.3 Consumer接口
java.util.function.Consumer
接口则正好与
Supplier
接口相反,它不是生产一个数据,而是
消费
一个数据,
其数据类型由泛型决定。
抽象方法:
accept
Consumer
接口中包含抽象方法
void accept(T t)
,意为消费一个指定泛型的数据。基本使用如:
当然,更好的写法是使用方法引用。
默认方法:
andThen
如果一个方法的参数和返回值全都是
Consumer
类型,那么就可以实现效果:消费数据的时候,
首先做一个操作,
然后再做一个操作,实现组合。而这个方法就是
Consumer
接口中的
default
方法
andThen
。下面
是
JDK
的源代码:
备注:
java.util.Objects
的
requireNonNull
静态方法将会在参数为
null
时主动抛出
NullPointerException
异常。这省去了重复编写
if
语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个
Lambda
表达式即可,而
andThen
的语义正是
“
一步接一步
”
操作。
例如两个步骤组
合的情况:
运行结果将会首先打印完全大写的
HELLO
,然后打印完全小写的
hello
。当然,通过链式写法可以
实现更多步骤的
组合。
3.4 练习:格式化打印信息
题目
下面的字符串数组当中存有多条信息,请按照格式
“
姓名:
XX
。性别:
XX
。
”
的格式将信息打印出
来。要求将打印姓
名的动作作为第一个 Consumer
接口的
Lambda
实例,将打印性别的动作作为第二个
Consumer
接
口的
Lambda
实
例,将两个
Consumer
接口按照顺序
“
拼接
”
到一起。
解答
3.5 Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个
boolean
值结果。这时可以使用
java.util.function.Predicate
接口。
抽象方法:
test
Predicate
接口中包含一个抽象方法:
boolean test(T t)
。用于条件判断的场景:
条件判断的标准是传入的
Lambda
表达式逻辑,只要字符串长度大于
5
则认为很长。
默认方法:
and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个
Predicate
条件使用
“
与
”
逻辑连接起来实
现
“
并且
”
的效果时,可以使用
default
方法
and
。其
JDK
源码为:
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:
默认方法:
or
与
and
的
“
与
”
类似,默认方法
or
实现逻辑关系中的
“
或
”
。
JDK
源码为:
如果希望实现逻辑
“
字符串包含大写
H
或者包含大写
W”
,那么代码只需要将
“and”
修改为
“or”
名称即
可,其他都不
变:
默认方法:
negate
“
与
”
、
“
或
”
已经了解了,剩下的
“
非
”
(取反)也会简单。默认方法
negate
的
JDK
源代码为:
从实现中很容易看出,它是执行了
test
方法之后,对结果
boolean
值进行
“!”
取反而已。一定要在
test
方法调用之前
调用
negate
方法,正如
and
和
or
方法一样:
3.6 练习:集合信息筛选
题目
数组当中有多条
“
姓名
+
性别
”
的信息如下,请通过
Predicate
接口的拼装将符合要求的字符串筛选到
集合
ArrayList
中,需要同时满足两个条件:
1.
必须为女生;
2.
姓名为
4
个字。
解答
3.7 Function接口
java.util.function.Function
接口用来根据一个类型的数据得到另一个类型的数据,前者称为
前置条件,
后者称为后置条件。
抽象方法:
apply
Function
接口中最主要的抽象方法为:
R apply(T t)
,根据类型
T
的参数获取类型
R
的结果。
使用的场景例如:将
String
类型转换为
Integer
类型。
当然,最好是通过方法引用的写法。
默认方法:
andThen
Function
接口中有一个默认的
andThen
方法,用来进行组合操作。
JDK
源代码如:
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
第一个操作是将字符串解析成为
int
数字,第二个操作是乘以
10
。两个操作通过
andThen
按照前后
顺序组合到了一
起。
请注意,
Function
的前置条件泛型和后置条件泛型可以相同。
3.8 练习:自定义函数模型拼接
题目
请使用
Function
进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = "
赵丽颖
,20";
1.
将字符串截取数字年龄部分,得到字符串;
2.
将上一步的字符串转换成为
int
类型的数字;
3.
将上一步的
int
数字累加
100
,得到结果
int
数字。
解答