【Java】函数式接口
【摘要】
🔎这里是【Java】,关注我学习Java不迷路 👍如果对你有帮助,给博主一个免费的点赞以示鼓励 欢迎各位🔎点赞👍评论收藏⭐️
👀专栏介绍
【Java】 目前主要更新Java,一起学习一起进步。
👀本期介绍
本期主要介绍函数式接口
文章目录
第一章 函数式接口
1.1 概念
1.2 格式
1.3 @Fun...
🔎这里是【Java】,关注我学习Java不迷路
👍如果对你有帮助,给博主一个免费的点赞以示鼓励
欢迎各位🔎点赞👍评论收藏⭐️
👀专栏介绍
【Java】 目前主要更新Java,一起学习一起进步。
👀本期介绍
本期主要介绍函数式接口
文章目录
第一章 函数式接口
1.1 概念
函数式接口在 Java 中是指: 有且仅有一个抽象方法的接口 。
函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda ,所以函数式接口就是可
以适用于 Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法, Java 中的 Lambda 才能顺利地进行推导。
备注: “ 语法糖 ” 是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的 for-each 语法,其实
底层的实现原理仍然是迭代器,这便是 “ 语法糖 ” 。从应用层面来讲, Java 中的 Lambda 可以被当做是匿名内部
类的 “ 语法糖 ” ,但是二者在原理上是不同的。
1.2 格式
只要确保接口中有且仅有一个抽象方法即可:
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:
1.3 @FunctionalInterface注解
与 @Override 注解的作用类似, Java 8 中专门为函数式接口引入了一个新的注解:
@FunctionalInterface 。该注
解可用于一个接口的定义上:
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将
会报错。需要 注
意 的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都
一样。
第二章 函数式编程
在兼顾面向对象特性的基础上, 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> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定
类型的对
象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要 “ 对外提供 ” 一个符合
泛型类型的对象
数据。
3.2 练习:求数组元素最大值
题目
使用 Supplier 接口作为方法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。提示:接口的
泛型请使用
java.lang.Integer 类。
解答
3.3 Consumer接口
java.util.function.Consumer<T> 接口则正好与 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<T> 接口。
抽象方法: 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<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为
前置条件,
后者称为后置条件。
抽象方法: apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型 T 的参数获取类型 R 的结果。
使用的场景例如:将 String 类型转换为 Integer 类型。
当然,最好是通过方法引用的写法。
默认方法: andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。 JDK 源代码如:
该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:
第一个操作是将字符串解析成为 int 数字,第二个操作是乘以 10 。两个操作通过 andThen 按照前后
顺序组合到了一
起。
请注意, Function 的前置条件泛型和后置条件泛型可以相同。
1. 将字符串截取数字年龄部分,得到字符串;
2. 将上一步的字符串转换成为 int 类型的数字;
3. 将上一步的 int 数字累加 100 ,得到结果 int 数字。
解答
文章来源: blog.csdn.net,作者:陶然同学,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_45481821/article/details/126314550
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)