一、函数式编程

  函数式编程,同面向对象编程、指令式编程一样,是一种软件编程范式,在多种编程语言中都有应用。百科词条中有很学术化的解释,但理解起来并不容易。不过,我们可以借助于数学中函数的概念,来理解函数式编程的要义所在。在数学中,我们常见的函数表达式形如 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的更多相关文章

  1. 函数式编程/lambda表达式入门

    函数式编程/lambda表达式入门 本篇主要讲解 lambda表达式的入门,涉及为什么使用函数式编程,以及jdk8提供的函数式接口 和 接口的默认方法 等等 1.什么是命令式编程 命令式编程就是我们去 ...

  2. python 函数对象(函数式编程 lambda、map、filter、reduce)、闭包(closure)

    1.函数对象 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 秉承着一切皆对象的理念,我们再次回头来看函数(function).函 ...

  3. 函数式编程--lambda表达式对比匿名内部类

    从前面的整理中我们看出了,Lambda表达式其实是匿名内部类的一种简化,因此它可以部分取代匿名内部类. 1,Lambda表达式与匿名内部类存在如下相同点: 1),Lambda表达式与匿名内部类一样,都 ...

  4. Python---进阶---函数式编程---lambda

    一. 利用map()函数,把用户输入的不规范的英文,变成首字母大写,其他小写的规范的名字:比如说["ADMAm", "LISA", "JACK&quo ...

  5. 简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他

    函数式编程 Lambda exec,eval和assert语句,repr函数   lambda语句 用来创建简短的单行匿名函数 print_assign = lambda name, value: n ...

  6. Python函数式编程:Lambda表达式

    首先我们要明白在编程语言中,表达式和语句的区别. 表达式是一个由变量.常量.有返回值的函数加运算符组成的一个式子,该式子是有返回值的 ,如  a + 1 就是个表达式, 单独的一个常量.变量 或函数调 ...

  7. C# 函数式编程 —— 使用 Lambda 表达式编写递归函数

    最近看了赵姐夫的这篇博客http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html,主要讲的是如何使用 Lambda 编写递归函数 ...

  8. Java 中的函数式编程(Functional Programming):Lambda 初识

    Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...

  9. 函数式编程--为什么会出现lambda表达式?

    java一直处在发张和演化的过程中,其中有2个版本从根本上改变了代码的编写方式.第一个就是JDK5之后增加的泛型,还有一个就是现在介绍的函数式编程,lambda表达式. lambda表达式是java8 ...

随机推荐

  1. bert+seq2seq 周公解梦,看AI如何解析你的梦境?【转】

    介绍 在参与的项目和产品中,涉及到模型和算法的需求,主要以自然语言处理(NLP)和知识图谱(KG)为主.NLP涉及面太广,而聚焦在具体场景下,想要生产落地的还需要花很多功夫. 作为NLP的主要方向,情 ...

  2. kafka connector 使用总结以及自定义connector开发

    Kafaka connect 是一种用于在Kafka和其他系统之间可扩展的.可靠的流式传输数据的工具.它使得能够快速定义将大量数据集合移入和移出Kafka的连接器变得简单.Kafka Connect可 ...

  3. 消除router-link 的下划线问题

    <div class="small-size"> <router-link to="/About"> <img src=" ...

  4. SQLServer2008R2(百度网盘)下载与安装教程

    很久没有安装过这个了,今天安装有点生疏了,这里记录一下分享 分为三块块1.下载地址,2.安装图解  ,3.安装失败问题 1.sqlserver 2008 r2 百度下载地址链接:下载 cn_sql_s ...

  5. Anaconda中启动Python时的错误:UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 553

    今天,在Anaconda prompt启动python遇到了如下错误: UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xaf in positi ...

  6. Linux -- 进程间通信几种方式的总结

    管道 优点 管道文件不占磁盘空间,打开管道时在内存中分配空间: 管道读端会在读取完管道内数据后自动进入阻塞,直到写端再次写入数据: 缺点 管道是半双工的,数据只能从一个方向上流动: 管道大小 PIPE ...

  7. 部署 kube-controller-manager 高可用集群

    目录 前言 创建kube-controller-manager证书和私钥 生成证书和私钥 将生成的证书和私钥分发到所有master节点 创建和分发kubeconfig文件 分发kubeconfig到所 ...

  8. MySQL 高可用架构 之 MHA (Centos 7.5 MySQL 5.7.18 MHA 0.58)

    目录 简介 环境准备 秘钥互信 安装基础依赖包 安装MHA组件 安装 MHA Node组件 安装 MHA Manager 组件 建立 MySQL 一主三从 初始化 MySQL 启动MySQL 并简单配 ...

  9. Nginx动静分离(Nginx+Tomcat)

    第一步:nginx构建 第二步:Tomcat构建 1.Tomcat基础点 (1)Tomcat 是基于java开发的web容器,用来发布java代码和jsp网页. (2)开发人员开发java web网站 ...

  10. 理解Redis的单线程模式

    0.概述 本文基于的Redis版本为4.0以下,在Redis更高版本中并不是完全的单线程了,增加了BIO线程,本文主要讲述主工作线程的单线程模式. 通过本文将了解到以下内容: Redis服务器采用单线 ...