函数式编程 -> Lambda
一、函数式编程
函数式编程,同面向对象编程、指令式编程一样,是一种软件编程范式,在多种编程语言中都有应用。百科词条中有很学术化的解释,但理解起来并不容易。不过,我们可以借助于数学中函数的概念,来理解函数式编程的要义所在。在数学中,我们常见的函数表达式形如 y=f(x),表示的是一种输入输出的映射关系:x表示输入,y表示输出,f 是表示两者之间的映射运算逻辑。在求值的时候,你完全不用考虑映射运算 f,只要给定输入 x,得到相应的输出 y;输入不变,输出也不会改变,就这么简单。类比到程序语言中来,所谓函数式编程,就是让我们以数学中函数映射的思想来编写出函数式的程序代码,让代码着重于输入和输出,而底层的映射处理逻辑,你完全可以当黑盒看待,这样,我们的业务关注点会更加清晰;而且,同数学函数一样,函数式编程的代码具有状态无关性——即相同的输入永远产生相同的输出,这在解决并发编程中共享变量状态一致性问题中有很大的应用场景。
在Java中,提到函数式编程,最先想到的肯定是Lambda表达式了(PS:切忌把Lambda表达式和函数式编程划等号,Lambda表达式只是符合这种函数式编程风格的匿名函数而已)。Lambda表达式在Java8中终于被重磅引入了(隔壁Python,C#,C++早就引入了哟喂),这让很多以前代码中的匿名写法得以通过函数式的代码进行极致的简化,有多简化呢?比如使用IDEA开发的时候,如果你选择的Java编译版本达到Java8的话,在编写匿名内部类的时候,编译器会不厌其烦的提示你将匿名写法替换成Lambda表达式——
你替换以后,原来几行的代码就简化成了下面这个样子——
这就是让你初见懵逼,再见着迷的函数式编程范例了。常有人说,相较于匿名内部类的写法,Lambda 表达式使代码更加简洁、易读。简洁确实简洁,毕竟减少了很多样板代码,非要说易读,博主是有些迟疑的。从语法上来说,除非你对Java8的这种新特性有过相当的学习,否则刚开始接触这种写法,你反而会有些凌乱,不解其意。喜感的是,这种只强调输入和输出的函数式编程风格其显著优点恰是让你一见代码就知其业务内容;你之所以会凌乱,在于其语法相较于我们面向对象编程中熟悉的类、接口、字段等概念太过新颖了,需要一定的学习成本。
在上面的代码示例中,我们之所以写匿名内部类,是因为在单一业务场景中,我们不想额外的编写接口的实现再去构造对象执行方法,而是直接创建匿名对象,执行完接口中的方法,对象的使命也就结束了。相较于先实现再建对象的方式,匿名内部类的写法算是一种代码的减省,虽然可读性差了些,但咱不怕!难受的一点在于,即使匿名写法,依然要遵循接口实现的规范,会多出很多样板式的代码;而实际上,我不过是想基于接口的方法定义去实现某种行为而已。也就是说,我的关注点在于接口的行为实现,而不是样板的语法层面。这个时候,Lambda表达式就得以大显身手了,它如你所愿,让你以函数式的编码风格,只关注行为本身的逻辑实现,其它的无关代码通通舍弃。就像上面的示例中,将传统的匿名写法改成 Lambda 表达式写法后,样板代码没了,简洁的代码让你一眼就能看出,你的代码要干什么。——这,就是Lambda!虽然初见时确实有些语法障碍,但在突破障碍之后,你会从心的喜欢这种编程方式——至少,在你的代码走位中应该适时的加入些 Lambda 这种风骚的‘姿势’了。
有人说,不就是代码简化嘛,语法糖而已啦。关于Lambda 表达式是不是语法糖的说法,又可以开篇讲义了。只能简单的说,Lambda 确实也是语法糖,但绝不是简单只为简化匿名内部类的写法的语法糖。当然,除了国内大神们的探究,你也可以去 Stack Overflow上提问或搜寻答案,看看老外们都怎么解释的。
二、Lambda
Lambda 表达式的个人理解,其实上文中已经给出了。现在,我们从语法层面,来说说实际项目中该如何编写基于 Lambda 的函数式风格代码。博主以上面的代码为例,整了一副草图,帮助你快速读懂 Lambda 语法:
这只是最简单的形式。空括号 () 表示没有输入参数,如果匿名接口有参数,你按照正常方法的参数定义编写即可,如 (Object o),(Object o1,Object o2)等。但由于Java7 开始就有了类型推断,通常我们是可以省略参数类型的,所以参数可以简化成 (o) ,(o1,o2)的形式,甚至在只有一个参数的时候,括号也能省略而只保留参数 o 。至于表达式的主体部分,也就是我们的业务代码,既可以是是一个表达式,如上面的一条打印,也可以是用花括号 { } 包围的一段代码块,具体以实际业务为准,当然,也要考虑代码的可读性。最后列举一些常见 Lambda 代码示例:
// 开启普通的线程任务
new Thread(() -> System.out.println("函数式编程——陈本布衣")).start();
// 可以是代码块的形式
new Thread(() ->{
System.out.println("函数式编程中的代码块");
System.out.println("函数式编程中的代码块");
System.out.println("函数式编程中的代码块");
System.out.println("函数式编程中的代码块");
}).start();
// 开启异步单线程,可获取线程任务返回值
Future<Integer> future = Executors.newSingleThreadExecutor().submit(() -> 10);
// GUI图形界面编程中的事件处理
new JButton().addActionListener((ActionEvent event) -> System.out.println("按钮点击事件"));
// 参数根据上下文推断,单参数可省略括号 ()
new JButton().addActionListener(event -> System.out.println("按钮点击事件"));
友情提示:由于 Lambda对代码的极致简化和新语法,初学者很难一步到位的写出正确的 Lambda 表达式代码,对初学者,比较好的实践建议先用匿名内部类的形式先实现,最后借助于IDE的快捷功能自动生成,待熟练之后,再装逼不迟!
三、函数接口
只学会了 Lambda 表达式的语法还远远不够,因为你不光要能手撸 Lambda 表达式代码,更重要的是你要搞清楚,在哪种场景下可以撸,哪种场景下无法撸,这是有讲究的。虽然上文中举了几个示例,但在实际应用中是远远不够的。博主说过,Lambda 表达式本质上是一个匿名函数,这么说,难道只要接口采用匿名类实现的地方,都可以使用Lambda 吗?答案当然是否定的!你可以亲自试一下,自己编写一个多方法的接口,也采用匿名实现,你看IDEA会不会那么热情的提醒你。
其实,在Java8 中伴随 Lambda 一起引入的,还有函数式接口这一概念。所谓函数式接口,是只有一个抽象方法的接口,只有这种接口才能被用来作为 Lambda 表达式的类型——也就是说,只有函数式接口的匿名实现,你才可以用 Lambda 表达式去改写代码。感觉这种限定缩窄了Lambda的应用范围,我上哪儿给你找那么多只有一个抽象函数的接口啊?有的,有的,而且还不少。Java8 为了支持函数式编程的应用场景,特意新增加了一个全是函数式接口的包 java.util.function,里面包含了四十多个函数式接口,足够你玩一阵子的了,而且这还不包括旧有的接口中符合函数接口定义的众多接口,如 Runnable,Comparator<T>,以及Java8中为了更便利的操作集合而新增的特性类库等。从 Java8 开始,你在源码中可以发现,无论旧有的和新引入的函数式接口,其接口声明上都会有 @FunctionalInterface 注解,该注解其实就是专门用来标注函数式接口的,算是一个标识注解。当然,也不是说函数接口就一定要用标示 @FunctionalInterface 注解来标注,只要符合只有一个抽象方法的接口定义,没有 @FunctionalInterface 标注也能成为 Lambda表达式的类型,只不过在接口上加上注解(尤其自己在定义函数式接口的时候),可以让编译器帮你检查错误。
函数接口,说这么多其实差不多就算完整了,但是且慢,博主还是要纠结一下:只有一个抽象方法的接口,是为函数式接口,那么,是不是不止一个抽象方法的接口,就一定不是抽象接口呢?上一段的阐述中,布衣博主故意列了一个 Comparator<T>接口,其在Java8 中的源码如下:
@FunctionalInterface
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); // 省略非抽象的 静态 和 默认方法
。。。
}
咦,这厮不对啊,有两个抽象接口,怎么也成了函数式接口?遇到这种情况,切莫慌张,找寻答案最好的方式,还是要从该接口声明的注释中去找,万一写源码的人搞错了呢?当然,错肯定是错不了,不过 Comparator 接口声明的注释中也没有给出合理的解释,还是只能从源头 @FunctionalInterface 注解的注释中去看看有没有答案。在 @FunctionalInterface 注解的注释文档中你会找到这样的描述:
If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere。
这算是很白话的英语了,简单翻译一下就是,因为所有接口都是默认继承了Object类的,所以,接口中如果有自 Object 类中继承而来的public方法,就不能算成抽象方法了。
好啦,对于函数式编程讲解的的开篇,算是讲完了。但这仅仅是开始,对于函数式编程这样一种新的编程尝试,还有很多值得学习和讨论的地方。后续博主会继续深入探究 Java8 中针对函数式编程引入的一些方法类库,以及这些新特性能给我们的编码带来哪些便利。
限于法力有限,只能粗浅讲解;欢迎挑刺,不胜感激。
函数式编程 -> Lambda的更多相关文章
- 函数式编程/lambda表达式入门
函数式编程/lambda表达式入门 本篇主要讲解 lambda表达式的入门,涉及为什么使用函数式编程,以及jdk8提供的函数式接口 和 接口的默认方法 等等 1.什么是命令式编程 命令式编程就是我们去 ...
- python 函数对象(函数式编程 lambda、map、filter、reduce)、闭包(closure)
1.函数对象 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 秉承着一切皆对象的理念,我们再次回头来看函数(function).函 ...
- 函数式编程--lambda表达式对比匿名内部类
从前面的整理中我们看出了,Lambda表达式其实是匿名内部类的一种简化,因此它可以部分取代匿名内部类. 1,Lambda表达式与匿名内部类存在如下相同点: 1),Lambda表达式与匿名内部类一样,都 ...
- Python---进阶---函数式编程---lambda
一. 利用map()函数,把用户输入的不规范的英文,变成首字母大写,其他小写的规范的名字:比如说["ADMAm", "LISA", "JACK&quo ...
- 简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他
函数式编程 Lambda exec,eval和assert语句,repr函数 lambda语句 用来创建简短的单行匿名函数 print_assign = lambda name, value: n ...
- Python函数式编程:Lambda表达式
首先我们要明白在编程语言中,表达式和语句的区别. 表达式是一个由变量.常量.有返回值的函数加运算符组成的一个式子,该式子是有返回值的 ,如 a + 1 就是个表达式, 单独的一个常量.变量 或函数调 ...
- C# 函数式编程 —— 使用 Lambda 表达式编写递归函数
最近看了赵姐夫的这篇博客http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html,主要讲的是如何使用 Lambda 编写递归函数 ...
- Java 中的函数式编程(Functional Programming):Lambda 初识
Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...
- 函数式编程--为什么会出现lambda表达式?
java一直处在发张和演化的过程中,其中有2个版本从根本上改变了代码的编写方式.第一个就是JDK5之后增加的泛型,还有一个就是现在介绍的函数式编程,lambda表达式. lambda表达式是java8 ...
随机推荐
- vant-ui的van-uploader上传图片
移动端上传图片是很常用的功能,这里使用vant-ui实现. 效果如图 上传图片的vue页面:Customer.vue html <div :class="postData.length ...
- Oracle10g安装步骤(一)
本例使用安装程序:10201_database_win32 首先将所有文件提取解压出来后,执行setup.exe 安装步骤如下:
- Magicodes.IE之导入学生数据教程
基础教程之导入学生数据 说明 本教程主要说明如果使用Magicodes.IE.Excel完成学生数据的Excel导入. 要点 本教程使用Magicodes.IE.Excel来完成Excel数据导入 需 ...
- MacOS安装Docker傻瓜式教程
最近电脑越来越卡了,为了减少系统开销,以及后期维护方便,所以考虑将本地安装一些服务迁移到docker中去管理,这一切的基础是要先有docker服务,所以本文就先记录怎样在mac上安装配置docker, ...
- SpringSecurity代码实现JWT接口权限授予与校验
通过笔者前两篇文章的说明,相信大家已经知道JWT是什么,怎么用,该如何结合Spring Security使用.那么本节就用代码来具体的实现一下JWT登录认证及鉴权的流程. 一.环境准备工作 建立Spr ...
- 数据降维-NMF非负矩阵分解
1.什么是非负矩阵分解? NMF的基本思想可以简单描述为:对于任意给定的一个非负矩阵V,NMF算法能够寻找到一个非负矩阵W和一个非负矩阵H,使得满足 ,从而将一个非负的矩阵分解为左右两个非负矩阵的乘积 ...
- Git实战指南----跟着haibiscuit学Git(第二篇)
笔名: haibiscuit 博客园: https://www.cnblogs.com/haibiscuit/ Git地址: https://github.com/haibiscuit?tab=re ...
- HDFS原理概念扫盲
1.概述 hdfs文件系统主要设计为了存储大文件的文件系统:如果有个TB级别的文件,我们该怎么存储呢?分布式文件系统未出现的时候,一个文件只能存储在个服务器上,可想而知,单个服务器根本就存储不了这么大 ...
- 以Python为例的Async / Await的编程基础
来源:Redislabs 作者:Loris Cro 翻译:Kevin (公众号:中间件小哥) 近年来,许多编程语言都在努力改进它们的并发原语.Go 语言有 goroutines,Ruby 有 fibe ...
- EF分页查询
/// <summary> /// 分页查询 + 条件查询 + 排序 /// </summary> /// <typeparam name="Tkey" ...