函数式编程 -> Lambda

1、函数式编程

  函数式编程,同面向对象编程、指令式编程同样,是一种软件编程范式,在多种编程语言中都有应用。百科词条中有很学术化的解释,但理解起来并不容易。不过,咱们能够借助于数学中函数的概念,来理解函数式编程的要义所在。在数学中,咱们常见的函数表达式形如 y=f(x),表示的是一种输入输出的映射关系:x表示输入,y表示输出,f 是表示二者之间的映射运算逻辑。在求值的时候,你彻底不用考虑映射运算 f,只要给定输入 x,获得相应的输出 y;输入不变,输出也不会改变,就这么简单。类比到程序语言中来,所谓函数式编程,就是让咱们以数学中函数映射的思想来编写出函数式的程序代码,让代码着重于输入和输出,而底层的映射处理逻辑,你彻底能够当黑盒看待,这样,咱们的业务关注点会更加清晰;并且,同数学函数同样,函数式编程的代码具备状态无关性——即相同的输入永远产生相同的输出,这在解决并发编程中共享变量状态一致性问题中有很大的应用场景。java

  在Java中,提到函数式编程,最早想到的确定是Lambda表达式了(PS:切忌把Lambda表达式和函数式编程划等号,Lambda表达式只是符合这种函数式编程风格的匿名函数而已)。Lambda表达式在Java8中终于被重磅引入了(隔壁Python,C#C++早就引入了哟喂),这让不少之前代码中的匿名写法得以经过函数式的代码进行极致的简化,有多简化呢?好比使用IDEA开发的时候,若是你选择的Java编译版本达到Java8的话,在编写匿名内部类的时候,编译器会不厌其烦的提示你将匿名写法替换成Lambda表达式——编程

 

  你替换之后,原来几行的代码就简化成了下面这个样子——segmentfault

  

  这就是让你初见懵逼,再见着迷的函数式编程范例了。常有人说,相较于匿名内部类的写法,Lambda 表达式使代码更加简洁、易读。简洁确实简洁,毕竟减小了不少样板代码,非要说易读,博主是有些迟疑的。从语法上来讲,除非你对Java8的这种新特性有过至关的学习,不然刚开始接触这种写法,你反而会有些凌乱,不解其意。喜感的是,这种只强调输入和输出的函数式编程风格其显著优势恰是让你一见代码就知其业务内容;你之因此会凌乱,在于其语法相较于咱们面向对象编程中熟悉的类、接口、字段等概念太过新颖了,须要必定的学习成本。 并发

  在上面的代码示例中,咱们之因此写匿名内部类,是由于在单一业务场景中,咱们不想额外的编写接口的实现再去构造对象执行方法,而是直接建立匿名对象,执行完接口中的方法,对象的使命也就结束了。相较于先实现再建对象的方式,匿名内部类的写法算是一种代码的减省,虽然可读性差了些,但咱不怕!难受的一点在于,即便匿名写法,依然要遵循接口实现的规范,会多出不少样板式的代码;而实际上,我不过是想基于接口的方法定义去实现某种行为而已。也就是说,个人关注点在于接口的行为实现,而不是样板的语法层面。这个时候,Lambda表达式就得以大显身手了,它如你所愿,让你以函数式的编码风格,只关注行为自己的逻辑实现,其它的无关代码统统舍弃。就像上面的示例中,将传统的匿名写法改为 Lambda 表达式写法后,样板代码没了,简洁的代码让你一眼就能看出,你的代码要干什么。——这,就是Lambda!虽然初见时确实有些语法障碍,但在突破障碍以后,你会从心的喜欢这种编程方式——至少,在你的代码走位中应该适时的加入些 Lambda 这种风骚的‘姿式’了。异步

  有人说,不就是代码简化嘛,语法糖而已啦。关于 Lambda 表达式是否是语法糖的说法,又能够开篇讲义了。只能简单的说,Lambda 确实也是语法糖,但毫不是简单只为简化匿名内部类的写法的语法糖。固然,除了国内大神们的探究,你也能够去 Stack Overflow上提问或搜寻答案,看看老外们都怎么解释的。编程语言

2、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的快捷功能自动生成,待熟练以后,再装逼不迟!性能

3、函数接口

  只学会了 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 中针对函数式编程引入的一些方法类库,以及这些新特性能给咱们的编码带来哪些便利。

  限于法力有限,只能粗浅讲解;欢迎挑刺,不胜感激。

相关文章
相关标签/搜索