Function源码解析与实践
作者:陈昌浩
1 导读
if…else…在代码中经常使用,听说可以通过Java 8的Function接口来消灭if…else…!Function接口是什么?如果通过Function接口接口消灭if…else…呢?让我们一起来探索一下吧。
2 Function接口
Function接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,Function接口可以被隐式转换为 lambda 表达式。可以通过FunctionalInterface注解来校验Function接口的正确性。Java 8允许在接口中加入具体方法。接口中的具体方法有两种,default方法和static方法。
@FunctionalInterface
interface TestFunctionService
{
void addHttp(String url);
}
那么就可以使用Lambda表达式来表示该接口的一个实现。
TestFunctionService testFunctionService = url -> System.out.println("http:" + url);
2.1 FunctionalInterface
2.1.1 源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
2.1.2 说明
上图是FunctionalInterface的注解说明。通过上面的注解说明,可以知道FunctionalInterface是一个注解,用来说明一个接口是函数式接口。 函数式接口只有一个抽象方法。 可以有默认方法,因为默认方法有一个实现,所以不是抽象的。函数接口的实例可以用lambda表达式、方法引用或构造函数引用创建。
FunctionalInterface会校验接口是否满足函数式接口:
- 类型必须是接口类型,不能是注释类型、枚举或类。
- 只能有一个抽象方法。
- 可以有多个默认方法和静态方法。
- 可以显示覆盖java.lang.Object中的抽象方法。
编译器会将满足函数式接口定义的任何接口视为函数式接口,而不管该接口声明中是否使用FunctionalInterface注解。
3 Function接口主要分类
Function接口主要分类:
- Function:Function函数的表现形式为接收一个参数,并返回一个值。
- Supplier:Supplier的表现形式为不接受参数、只返回数据。
- Consumer:Consumer接收一个参数,没有返回值。
- Runnable:Runnable的表现形式为即没有参数也没有返回值。
3.1 Function
Function函数的表现形式为接收一个参数,并返回一个值。
3.1.1 源码
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
3.1.2 方法说明
- apply:抽象方法。将此函数应用于给定的参数。参数t通过具体的实现返回R。
- compose:default方法。返回一个复合函数,首先执行fefore函数应用于输入,然后将该函数应用于结果。如果任意一个函数的求值引发异常,则将其传递给组合函数的调用者。
- andThen:default方法。返回一个复合函数,该复合函数首先对其应用此函数它的输入,然后对结果应用after函数。如果任意一个函数的求值引发异常,则将其传递给组合函数的调用者。
- identity:static方法。返回一个始终返回其输入参数的函数。
3.1.3 方法举例
1)apply
测试代码:
public String upString(String str){
Function<String, String> function1 = s -> s.toUpperCase();
return function1.apply(str);
}
public static void main(String[] args) {
System.out.println(upString("hello!"));
}
通过apply调用具体的实现。执行结果:
2)compose
测试代码:
public static void main(String[] args) {
Function<String, String> function1 = s -> s.toUpperCase();
Function<String, String> function2 = s -> "my name is "+s;
String result = function1.compose(function2).apply("zhangSan");
System.out.println(result);
}
执行结果
如结果所示:compose 先执行function2 后执行function1。
3)andThen
测试代码:
public static void main(String[] args) {
Function<String, String> function1 = s -> s.toUpperCase();
Function<String, String> function2 = s -> "my name is "+s;
String result = function1.andThen(function2).apply("zhangSan");
System.out.println(result);
}
执行结果:
如结果所示:
andThen先执行function1 后执行function2。
- identity
测试代码:
public static void main(String[] args) {
Stream<String> stream = Stream.of("order", "good", "lab", "warehouse");
Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
System.out.println(map);
}
执行结果:
3.2 Supplier
Supplier的表现形式为不接受参数、只返回数据。
3.2.1 源码
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
3.2.2 方法说明
get:抽象方法。通过实现返回T。
3.2.3 方法举例
public class SupplierTest {
SupplierTest(){
System.out.println(Math.random());
System.out.println(this.toString());
}
}
public static void main(String[] args) {
Supplier<SupplierTest> sup = SupplierTest::new;
System.out.println("调用一次");
sup.get();
System.out.println("调用二次");
sup.get();
}
执行结果:
如结果所示:Supplier建立时并没有创建新类,每次调用get返回的值不是同一个。
3.3 Consumer
Consumer接收一个参数,没有返回值。
3.3.1 源码
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
3.3.2 方法说明
- accept:对给定参数T执行一些操作。
- andThen:按顺序执行Consumer -> after ,如果执行操作引发异常,该异常被传递给调用者。
3.3.3 方法举例
public static void main(String[] args) {
Consumer<String> consumer = s -> System.out.println("consumer_"+s);
Consumer<String> after = s -> System.out.println("after_"+s);
consumer.accept("isReady");
System.out.println("========================");
consumer.andThen(after).accept("is coming");
}
执行结果:
如结果所示:对同一个参数T,通过andThen 方法,先执行consumer,再执行fater。
3.4 Runnable
Runnable:Runnable的表现形式为即没有参数也没有返回值。
3.4.1 源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
3.4.2 方法说明
run:抽象方法。run方法实现具体的内容,需要将Runnale放入到Thread中,通过Thread类中的start()方法启动线程,执行run中的内容。
3.4.3 方法举例
public class TestRun implements Runnable {
@Override
public void run() {
System.out.println("TestRun is running!");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new TestRun());
thread.start();
}
执行结果:
如结果所示:当线程实行start方法时,执行Runnable 的run方法中的内容。
4 Function接口用法
Function的主要用途是可以通过lambda 表达式实现方法的内容。
4.1 差异处理
原代码:
@Data
public class User {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 组员
*/
private List<User> parters;
}
public static void main(String[] args) {
User user =new User();
if(user ==null ||user.getAge() <18 ){
throw new RuntimeException("未成年!");
}
}
执行结果:
使用Function接口后的代码:
@FunctionalInterface
public interface testFunctionInfe {
/**
* 输入异常信息
* @param message
*/
void showExceptionMessage(String message);
}
public static testFunctionInfe doException(boolean flag){
return (message -> {
if (flag){
throw new RuntimeException(message);
}
});
}
public static void main(String[] args) {
User user =new User();
doException(user ==null ||user.getAge() <18).showExceptionMessage("未成年!");
}
执行结果:
使用function接口前后都抛出了指定的异常信息。
4.2 处理if…else…
原代码:
public static void main(String[] args) {
User user =new User();
if(user==null){
System.out.println("新增用户");
}else {
System.out.println("更新用户");
}
}
使用Function接口后的代码:
public static void main(String[] args) {
User user =new User();
Consumer trueConsumer = o -> {
System.out.println("新增用户");
};
Consumer falseConsumer= o -> {
System.out.println("更新用户");
};
trueOrFalseMethdo(user).showExceptionMessage(trueConsumer,falseConsumer);
}
public static testFunctionInfe trueOrFalseMethdo(User user){
return ((trueConsumer, falseConsumer) -> {
if(user==null){
trueConsumer.accept(user);
}else {
falseConsumer.accept(user);
}
});
}
@FunctionalInterface
public interface testFunctionInfe {
/**
* 不同分处理不同的事情
* @param trueConsumer
* @param falseConsumer
*/
void showExceptionMessage(Consumer trueConsumer,Consumer falseConsumer);
}
执行结果:
4.3 处理多个if
原代码:
public static void main(String[] args) {
String flag="";
if("A".equals(flag)){
System.out.println("我是A");
}else if ("B".equals(flag)) {
System.out.println("我是B");
}else if ("C".equals(flag)) {
System.out.println("我是C");
}else {
System.out.println("没有对应的指令");
}
}
使用Function接口后的代码:
public static void main(String[] args) {
String flag="B";
Map<String, Runnable> map =initFunctionMap();
trueOrFalseMethdo(map.get(flag)==null).showExceptionMessage(()->{
System.out.println("没有相应指令");
},map.get(flag));
}
public static Map<String, Runnable> initFunctionMap(){
Map<String,Runnable> result = Maps.newHashMap();
result.put("A",()->{System.out.println("我是A");});
result.put("B",()->{System.out.println("我是B");});
result.put("C",()->{System.out.println("我是C");});
return result;
}
public static testFunctionInfe trueOrFalseMethdo(boolean flag){
return ((runnable, falseConsumer) -> {
if(flag){
runnable.run();
}else {
falseConsumer.run();
}
});
}
执行结果:
5 总结
Function函数式接口是java 8新加入的特性,可以和lambda表达式完美结合,是非常重要的特性,可以极大的简化代码。
Function源码解析与实践的更多相关文章
- Optional源码解析与实践
1 导读 NullPointerException在开发过程中经常遇到,稍有不慎小BUG就出现了,如果避免这个问题呢,Optional就是专门解决这个问题的类,那么Optional如何使用呢?让我们一 ...
- Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- [Spark內核] 第42课:Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践
本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 ...
- Istio技术与实践02:源码解析之Istio on Kubernetes 统一服务发现
前言 文章Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制结合Pilot的代码实现介绍了Istio的抽象服务模型和基于该模型的数据结构定义,了解到Istio上只是定义的服务发现的接 ...
- 美团动态线程池实践思路开源项目(DynamicTp),线程池源码解析及通知告警篇
大家好,这篇文章我们来聊下动态线程池开源项目(DynamicTp)的通知告警模块.目前项目提供以下通知告警功能,每一个通知项都可以独立配置是否开启.告警阈值.告警间隔时间.平台等,具体代码请看core ...
- Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践
本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 ...
- httprunner开发实践&源码解析
上次作业讲解 排错 控制台查看报错信息 打开代理工具,调试脚本 注释掉其他接口,先跑一个接口 pip uninstall httprunner 修复断言100为int型问题 修复两次登陆问题 报告 p ...
- jQuery2.x源码解析(DOM操作篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...
- [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer
[源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer 目录 [源码解析] 深度学习分布式训练框架 horovod (7) --- Distri ...
- [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark
[源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark 目录 [源码解析] 深度学习分布式训练框架 horovod (10) --- run on spark ...
随机推荐
- DFS文件夹无法访问
最近DFS的文件服务器出现了部分文件和文件夹无法访问的情况.客户端直接访问DFS成员的共享文件夹时有是会出现Element not found的错误.有时打开文件的时候会出现文件不存在,或者你没有权限 ...
- (数据科学学习手札143)为geopandas添加gdb文件写出功能
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,很多读者朋友跟随着我先前写作的 ...
- 【COS生态建设】开发者有奖调研,等你来参与!
为了更好的赋能开发者,为大家提供更好的开源应用,我们诚挚的邀请您抽出几分钟参与"有奖问卷",告诉我们您对"COS生态建设"的意见和建议.希望通过这份调查问卷,能 ...
- 2.69分钟完成BERT训练!新发CANN 5.0加持
摘要:快,着实有点快. 现在,经典模型BERT只需2.69分钟.ResNet只需16秒. 啪的一下,就能完成训练! 本文分享自华为云社区<这就是华为速度:2.69分钟完成BERT训练!新发CAN ...
- proxysql 开启http监控页面的方法
update global_variables set variable_value='true' where variable_name='admin-web_enabled'; LOAD ADMI ...
- 普通用户使用CI/CD权限使用
根据文章:授权用户访问名称空间 (https://www.cnblogs.com/sanduzxcvbnm/p/15015576.html) 进行有关操作后,普通用户点击 会报错如下信息: 解决办法: ...
- C#并发编程-3 并行编程基础
如果程序中有大量的计算任务,并且这些任务能分割成几个互相独立的任务块,那就应该使用并行编程. 并行编程用于分解计算密集型的任务片段,并将它们分配给多个线程.这些并行处理方法只适用于计算密集型的任务. ...
- C++面向对象编程之虚指针、虚表
1.当编译器看到一个函数调用,有2个考量:静态绑定or动态绑定 静态绑定是"call xxx",xxx 是表示地址,call 是汇编语言的一个动作,它一定会调用到某个地址: 当符合 ...
- 一天五道Java面试题----第七天(mysql索引结构,各自的优劣--------->事务的基本特性和隔离级别)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 1 .mysql索引结构,各自的优劣 2 .索引的设计原则 3 .mysql锁的类型有哪些 4 .mysql执行计划怎么看 ...
- 齐博x1第三季《模板风格的制作》系列009-自定义区块代码
本节来说明如何自定义区块代码,不再继承上层模板,实现个性模板 上一节因为我们继承了layout布局模版,所以我们自定义的代码就无效了 如果我们继承了上层模板,那么相当于我们复制了一份上层模板的结构,也 ...