背??景长期以来,Java一直是一种面向对象的语言。一切都是对象。如果要调用一个函数,该函数必须属于一个类或对象,然后使用该类或对象来调用。但是在其他的编程语言中,比如JS、C++,我们可以直接写一个函数,然后在需要的时候调用,可以说是面向对象编程或者函数式编程。从功能的角度来看,面向对象编程没有错,但是从开发的角度来看,面向对象编程会写很多行可能重复的代码。比如创建一个Runnable匿名类时:Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){System.out.println("dosomething...");}};这段代码中唯一真正有用的是run方法中的内容,其余的都是Java编程语言的结构部分,没有什么用,但是必须要写。幸运的是,从Java8开始,引入了函数式编程接口和Lambda表达式,帮助我们写出更少、更优雅的代码://一行就够了Runnablerunnable=()->System.out.println("dosomething...");现在主要有三种主流编程范式,面向过程、面向对象和函数式编程。函数式编程并不是什么新鲜事物,它在50多年前就已经出现了。近年来,函数式编程越来越受到关注,涌现出很多新的函数式编程语言,比如Clojure、Scala、Erlang等,一些非函数式编程语言也加入了很多特性、语法、以及支持函数式编程的类库,例如Java、Python、Ruby、JavaScript等。此外,GoogleGuava还对函数式编程进行了增强。由于编程的特殊性,函数式编程只能在科学计算、数据处理、统计分析等领域充分发挥其优势,因此不能完全取代更通用的面向对象编程范式。但作为一种补充,它对于生存、发展和学习也有着重大的意义。什么是函数式编程函数式编程的英文翻译是FunctionalProgramming。那么函数式编程到底是什么?事实上,函数式编程并没有一个严格的官方定义。严格来说,函数式编程中的“函数”并不是指我们编程语言中的“函数”这个概念,而是指一个数学上的“函数”或“表达式”(例如:y=f(x))。但是,在实现编程时,对于数学上的“函数”或者“表达式”,我们一般习惯性地设计成函数。因此,如果不深究的话,函数式编程中的“函数”也可以理解为编程语言中的“函数”。每个编程范式都有其独特的方式,这就是为什么它们被抽象为范式的原因。面向对象程序设计的最大特点是:以类和对象为组织代码的单位及其四大特点。面向过程编程最大的特点是以函数作为代码组织的单位,数据和方法是分离的。函数式编程最独特的地方在哪里?其实,函数式编程最独特的地方在于它的编程思想。函数式编程假设程序可以表示为一系列数学函数或表达式的组合。函数式编程是对数学编程的低级抽象,将计算过程描述为表达式。不过,你这么一说,肯定会有疑惑。任何程序都可以表示为一组数学表达式吗?从理论上讲,这是可能的。但是,并非所有程序都适用于此。函数式编程有自己适合的应用场景,比如科学计算、数据处理、统计分析等,在这些领域中,程序往往更容易用数学表达式来表达。与非函数式编程相比,要实现同样的功能,函数式编程只需要很少的代码就可以完成。但是,对于业务关联性强的大型业务系统的开发,如果难以将其抽象成数学表达式,硬要用函数式编程来实现,显然是自找麻烦。相反,在这种应用场景下,面向对象编程更适合,写出的代码也更具可读性和可维护性。在编程实现上,函数式编程和面向过程编程一样,也是以函数为单位来组织代码。但是,它与过程编程的不同之处在于它的功能是无状态的。什么是无国籍?简单来说,函数中涉及的变量都是局部变量。他们不会像面向对象编程那样共享类成员变量,也不会像面向过程编程那样共享全局变量。函数的执行结果只与输入参数有关,与其他任何外部变量无关。同样的输入参数,无论怎么执行,结果都是一样的。这其实是对数学函数或者数学表达式的基本要求。例如://statefulfunction:执行结果取决于b的值,即使输入参数相同,//多次执行函数时,函数的返回值也可能不同,因为b的值b可能不同。intb;intincrease(inta){returna+b;}//无状态函数:执行结果不依赖于任何外部变量值//只要输入参数相同,无论执行多少次,函数的返回值是一样的intincrease(inta,intb){returna+b;}不同的编程范式并不完全不同,总有一些相同的编程规则。比如无论是面向过程、面向对象还是函数式编程,它们都有变量和函数的概念,顶层必须有一个main函数执行入口来组装编程单元(类、函数等).但是,面向对象的编程单元是类或对象,面向过程的编程单元是函数,而函数式编程的编程单元是无状态函数。Java对函数式编程的支持面向对象编程的实现不一定要使用面向对象的编程语言。同样,也不一定非要使用函数式编程语言来实现函数式编程。现在,很多面向对象的编程语言也提供了相应的语法和类库来支持函数式编程。Java这种面向对象的编程语言通过一个例子支持函数式编程:publicclassDemo{publicstaticvoidmain(String[]args){Optionalresult=Stream.of("a","be","hello").map(s->s.length()).filter(l->l<=3).max((o1,o2)->o1-o2);System.out.println(result.get());//Output2}}这段代码的作用是从一组字符串数组中筛选出长度小于等于3的字符串,并找出其中长度最大的字符串。Java为函数式编程引入了三个新的语法概念:Stream类、Lambda表达式和函数式接口。Stream类用于支持通过“.”级联多个函数操作的代码编写方式;引入Lambda表达式的作用是简化代码编写;函数接口的作用就是让我们把函数包装成一个函数接口,实现函数将其作为参数使用(Java不像C那样支持函数指针,可以直接将函数作为参数使用)。Stream类假定我们要计算这样一个表达式:(3-1)*2+5。如果按照普通函数调用的方式写成这样:add(multiply(subtract(3,1),2),5);不过这样写代码会显得难以理解,我们换一种可读性更好的写法如下:subtract(3,1).multiply(2).add(5);在Java中,“.”意味着调用对象的方法。为了支持上面的级联调用方式,我们让每个函数返回一个通用的Stream类对象。Stream类有两种类型的操作:中间操作和终止操作。中间操作仍然返回一个Stream类对象,而终止操作返回一个定值结果。我们再看一下前面的例子,用注释来解释代码。其中map和filter是中间操作,返回一个Stream类对象,可以继续级联其他操作;max是终止操作,返回的对象不是Stream类对象,无法继续级联处理。publicclassDemo{publicstaticvoidmain(String[]args){Optionalresult=Stream.of("f","ba","hello")//of返回Streamobject.map(s->s.length())//map返回Stream对象。filter(l->l<=3)//过滤器返回Stream对象。max((o1,o2)->o1-o2);//max终止操作:returnOptionalSystem.out.println(result.get());//Output2}}Lambdaexpression如前所述,Java引入Lambda表达式的主要作用是简化代码编写。其实我们也可以不使用Lambda表达式来编写示例中的代码。我们以地图功能为例。下面三段代码,第一段代码展示了map函数的定义,其实map函数接收的参数是一个Function接口,即函数接口。第二段代码显示了如何使用map函数。第三段代码是将第二段代码用Lambda表达式简化后写的。其实Lambda表达式只是Java中的一个语法糖,底层是基于函数接口实现的,也就是第二个代码所示的写法。//Stream类中map函数的定义:publicinterfaceStreamextendsBaseStream>{Streammap(Functionmapper);//...Otherfunctions...}//在Stream类中使用map的例子:Stream.of("fo","bar","hello").map(newFunction(){@OverridepublicIntegerapply(Strings){returns.length();}});//用Lambda表达式简化:Stream.of("fo","bar","hello").map(s->s.length());ALambda表达式包括三部分:输入、函数体和输出。表达式如下:(a,b)->{statement1;statement2;...;returnoutput;}//a,b为入参其实Lambda表达式的写法非常灵活。以上是标准的写法,还有很多简化的写法。比如只有一个入参,()可以省略,直接写成a->{...};如果没有输入参数,输入和箭头可以直接省略,只保留函数体;如果函数体只有一条语句,则{}可以省略;如果函数没有返回值,return语句可以省略。Optionalresult=Stream.of("f","ba","hello").map(s->s.length()).filter(l->l<=3).max((o1,o2)->o1-o2);//还原函数接口的实现Optionalresult2=Stream.of("fo","bar","hello").map(newFunction(){@OverridepublicIntegerapply(Strings){returns.length();}}).filter(newPredicate(){@Overridepublicbooleantest(Integerl){returnl<=3;}}).max(newComparator(){@Overridepublicintcompare(Integero1,Integero2){returno1-o2;}});Lambda表达式与匿名类的异同主要体现在以下三点:Lambda为优化匿名内部类而生,Lambda比匿名类简洁得多。Lambda仅适用于函数式接口,匿名类不受限制。即匿名类中的this就是“匿名类对象”本身;Lambda表达式中的this指的是“调用Lambda表达式的对象”。函数式接口其实上面代码中的Function、Predicate、Comparator都是函数式接口。我们知道C语言支持函数指针,可以直接把函数当作变量使用。但是,Java没有这样的函数指针语法。所以它使用函数接口,将函数包装在接口中,作为变量使用。事实上,函数式接口就是一个接口。不过它也有自己特殊的地方,就是要求只包含一个未实现的方法。因为只有这样,Lambda表达式才能清楚的知道匹配的是哪个方法。如果有两个未实现的方法,并且接口入参和返回值相同,那么Java在翻译Lambda表达式的时候就不知道这个表达式对应的是哪个方法。函数式接口也是Java接口的一种,但仍需满足:一个函数式接口只有一个抽象方法(singleabstractmethod);Object类中的公共抽象方法不会被视为单个抽象方法;函数式接口可以有默认方法和静态方法;功能接口可以用@FunctionalInterface注释装饰。满足这些条件的接口可以被认为是功能接口。比如Java8中的Comparator接口:@FunctionalInterfacepublicinterfaceComparator{/***singleabstractmethod*@since1.8*/intcompare(To1,To2);/***publicabstractmethod*@since1.8*/booleanequals中对象类(Objectobj);/***默认方法*@since1.8*/defaultComparatorreversed(){returnCollections.reverseOrder(this);}/***静态方法*@since1.8*/publicstatic>ComparatorreverseOrder(){returnCollections.reverseOrder();}//省略...}函数式接口有什么用?总之,函数式接口最大的好处就是:可以使用极简的lambda表达式实例化接口。你为什么这么说?我们或多或少用过一些只有一个抽象方法的接口,比如Runnable、ActionListener、Comparator等,比如我们要用Comparator来实现一个排序算法,我们的处理方法通常无外乎两种:规范适当编写一个实现了Comparator接口的Java类来封装排序逻辑。如果业务需要多种排序方式,就得写多个类来提供多种实现,而这些实现往往只需要使用一次。另一种聪明的方法是在需要的地方创建一个匿名内部类,例如:{@Overridepublicintcompare(Persono1,Persono2){returnInteger.compareTo(o1.getAge(),o2.getAge());}});}}匿名内部类实现的代码量不多Go,结构是相当清楚。Comparator接口在Jdk1.8中实现了一个FunctionalInterface注解,这意味着Comparator是一个函数式接口,用户可以安全地通过lambda表达式实例化它。然后让我们看一下使用lambda表达式快速新建自定义比较器需要编写的代码:Comparatorcomparator=(p1,p2)->Integer.compareTo(p1.getAge(),p2.getAge());->前面的()是Comparator接口中compare方法的参数列表,->后面是compare方法的方法体。Java提供的两个函数式接口Function和Predicate的源码摘录如下:@FunctionalInterfacepublicinterfaceFunction{Rapply(Tt);//只有一个未实现的方法defaultFunctioncompose(Functionbefore){Objects.requireNonNull(before);return(Vv)->apply(before.apply(v));}defaultFunctionandThen(Functionafter){Objects.requireNonNull(after);return(Tt)->after.apply(apply(t));}staticFunctionidentity(){return->t;}}@FunctionalInterfacepublicinterfacePredicate{booleantest(Tt);//只有这一个未实现的方法defaultPredicateand(Predicateother){Objects.requireNonNull(other);return(t)->test(t)&&other.test(t);}defaultPredicatenegate(){return(t)->!test(t);}defaultPredicateor(Predicateother){对象.requireNonNull(other);return(t)->test(t)||other.test(t);}staticPredicateisEqual(ObjecttargetRef){return(null==targetRef)?Objects::isNull:object->targetRef.equals(object);}}@FunctionalInterface注解使用场景我们知道,一个接口只要满足只有having的条件就可以作为函数式接口使用一种抽象方法。有没有@FunctionalInterface无所谓,但jdk定义这个注解肯定是有原因的。对于开发者来说,在使用这个注解之前一定要三思。如果使用这个注解,在接口中添加抽象方法,编译器会报错,编译失败。换句话说,@FunctionalInterface是一个承诺,只有这个抽象方法会在这个接口中世代存在。因此,开发人员可以使用Lambda实例化任何使用此注解的接口。当然,滥用@FunctionalInterface的后果是极其灾难性的:如果有一天你去掉这个注解,再加上一个抽象方法,那么所有使用Lambda实例化接口的客户端代码都会编译错误。特别是当一个接口只有一个抽象方法,但没有用@FunctionalInterface注解修饰时,就意味着别人没有承诺该接口以后不会添加抽象方法,所以建议不要使用Lambda来实例化,还是老??老实实用前面的方法比较稳定。总结函数式编程更符合数学函数映射的思想。具体到编程语言层面,我们可以使用Lambda表达式快速编写函数映射,通过链式调用将函数连接在一起,完成所需的业务逻辑。后来引入了Java的Lambda表达式。由于函数式编程在并行处理方面的优势,被广泛应用于大数据计算领域。