概述背景 函数式编程的理论基础是阿隆佐丘奇(AlonzoChurch)于1930年代提出的演算(LambdaCalculus)。演算是一种形式系统,用于研究函数定义、函数应用和递归。它为计算理论和计算机科学的发展奠定了基础。随着Haskell(1990年)和Erlang(1986年)等新一代函数式编程语言的诞生,函数式编程开始在实际应用中发挥作用。函数式的价值 随着硬件越来越便宜,程序的规模和复杂性都在呈线性的增长。这一切都让编程工作变得困难重重。我们想方设法使代码更加一致和易懂。我们急需一种语法优雅,简洁健壮,高并发,易于测试和调试的编程方式,这一切恰恰就是函数式编程(FP)的意义所在。 函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用。例如:如今Python,Java8都在吸收FP的思想,并且将其融入其中,你也可以这样想: OO(objectoriented,面向对象)是抽象数据,FP(functionalprogramming,函数式编程)是抽象行为。新旧对比 用传统形式和Java8的方法引用、Lambda表达式分别演示。代码示例:interfaceStrategy{Stringapproach(Stringmsg);}classSoftimplementsStrategy{publicStringapproach(Stringmsg){returnmsg。toLowerCase()?;}}classUnrelated{staticStringtwice(Stringmsg){returnmsgmsg;}}publicclassStrategize{Strategystrategy;Stringmsg;Strategize(Stringmsg){strategynewSoft();〔1〕构建默认的Softthis。msgmsg;}voidcommunicate(){System。out。println(strategy。approach(msg));}voidchangeStrategy(Strategystrategy){this。strategystrategy;}publicstaticvoidmain(String〔〕args){Strategy〔〕strategies{newStrategy(){〔2〕Java8以前的匿名内部类publicStringapproach(Stringmsg){returnmsg。toUpperCase()!;}},msgmsg。substring(0,5),〔3〕基于Ldmbda表达式,实例化interfaceUnrelated::twice〔4〕基于方法引用,实例化interface};StrategizesnewStrategize(Hellothere);s。communicate();for(StrategynewStrategy:strategies){s。changeStrategy(newStrategy);〔5〕使用默认的Soft策略s。communicate();〔6〕每次调用communicate()都会产生不同的行为}}} 输出结果:hellothere?HELLOTHERE!HelloHellothereHellothereLambda表达式 Lambda表达式是使用最小可能语法编写的函数定义:(原则)Lambda表达式产生函数,而不是类Lambda语法尽可能少,这正是为了使Lambda易于编写和使用 Lambda用法:interfaceDescription{Stringbrief();}interfaceBody{Stringdetailed(Stringhead);}interfaceMulti{StringtwoArg(Stringhead,Doubled);}publicclassLambdaExpressions{staticBodybodhhNoParens!;〔1〕一个参数时,可以不需要扩展(),但这是一个特例staticBodybod2(h)hMoredetails;〔2〕正常情况下的使用方式staticDescriptiondesc()Shortinfo;〔3〕没有参数的情况下的使用方式staticMultimult(h,n)hn;〔4〕多参数情况下的使用方式staticDescriptionmoreLines(){〔5〕多行代码情况下使用{}return关键字(在单行的Lambda表达式中return是非法的)System。out。println(moreLines());returnfrommoreLines();};publicstaticvoidmain(String〔〕args){System。out。println(bod。detailed(Oh!));System。out。println(bod2。detailed(Hi!));System。out。println(desc。brief());System。out。println(mult。twoArg(Pi!,3。14159));System。out。println(moreLines。brief());}} 输出结果:Oh!NoParens!Hi!MoredetailsShortinfoPi!3。14159moreLines()frommoreLines() 总结:Lambda表达式通常比匿名内部类产生更易读的代码,因此我们将尽可能使用它们。方法引用 方法引用由类名或者对象名,后面跟着,然后跟方法名称, 使用示例:interfaceCallable{〔1〕单一方法的接口(重要)voidcall(Strings);}classDescribe{voidshow(Stringmsg){〔2〕符合Callable接口的call()方法实现System。out。println(msg);}}publicclassMethodReferences{staticvoidhello(Stringname){〔3〕也符合call()方法实现System。out。println(Hello,name);}staticclassDescription{Stringabout;Description(Stringdesc){aboutdesc;}voidhelp(Stringmsg){〔4〕静态类的非静态方法System。out。println(aboutmsg);}}staticclassHelper{staticvoidassist(Stringmsg){〔5〕静态类的静态方法,符合call()方法System。out。println(msg);}}publicstaticvoidmain(String〔〕args){DescribednewDescribe();Callablecd::show;〔6〕通过方法引用创建Callable的接口实现c。call(call());〔7〕通过该实例call()方法调用show()方法cMethodReferences::hello;〔8〕静态方法的方法引用c。call(Bob);cnewDescription(valuable)::help;〔9〕实例化对象的方法引用c。call(information);cHelper::assist;〔10〕静态方法的方法引用c。call(Help!);}} 输出结果:call()Hello,BobvaluableinformationHelp!Runnable接口 使用Lambda和方法引用改变Runnable接口的写法:方法引用与Runnable接口的结合使用classGo{staticvoidgo(){System。out。println(Go::go());}}publicclassRunnableMethodReference{publicstaticvoidmain(String〔〕args){newThread(newRunnable(){publicvoidrun(){System。out。println(Anonymous);}})。start();newThread(()System。out。println(lambda))。start();newThread(Go::go)。start();通过方法引用创建Runnable实现的引用}} 输出结果:AnonymouslambdaGo::go()未绑定的方法引用 使用未绑定的引用时,需要先提供对象:未绑定的方法引用是指没有关联对象的普通方法classX{Stringf(){returnX::f();}}interfaceMakeString{Stringmake();}interfaceTransformX{Stringtransform(Xx);}publicclassUnboundMethodReference{publicstaticvoidmain(String〔〕args){MakeStringspX::f;〔1〕你不能在没有X对象参数的前提下调用f(),因为它是X的方法TransformXspX::f;〔2〕你可以首个参数是X对象参数的前提下调用f(),使用未绑定的引用,函数式的方法不再与方法引用的签名完全相同XxnewX();System。out。println(sp。transform(x));〔3〕传入x对象,调用x。f()方法System。out。println(x。f());同等效果}} 输出结果:X::f()X::f() 我们通过更多示例来证明,通过未绑的方法引用和interface之间建立关联:packagecom。github。xiao2shiqi。lambda;未绑定的方法与多参数的结合运用classThis{voidtwo(inti,doubled){}voidthree(inti,doubled,Strings){}voidfour(inti,doubled,Strings,charc){}}interfaceTwoArgs{voidcall2(Thisathis,inti,doubled);}interfaceThreeArgs{voidcall3(Thisathis,inti,doubled,Strings);}interfaceFourArgs{voidcall4(Thisathis,inti,doubled,Strings,charc);}publicclassMultiUnbound{publicstaticvoidmain(String〔〕args){TwoArgstwoargsThis::two;ThreeArgsthreeargsThis::three;FourArgsfourargsThis::four;ThisathisnewThis();twoargs。call2(athis,11,3。14);threeargs。call3(athis,11,3。14,Three);fourargs。call4(athis,11,3。14,Four,Z);}}构造函数引用 可以捕获构造函数的引用,然后通过引用构建对象classDog{Stringname;intage1;ForunknownDog(){namestray;}Dog(Stringnm){namenm;}Dog(Stringnm,intyrs){namenm;ageyrs;}}interfaceMakeNoArgs{Dogmake();}interfaceMake1Arg{Dogmake(Stringnm);}interfaceMake2Args{Dogmake(Stringnm,intage);}publicclassCtorReference{publicstaticvoidmain(String〔〕args){通过::new关键字赋值给不同的接口,然后通过make()构建不同的实例MakeNoArgsmnaDog::new;〔1〕将构造函数的引用交给MakeNoArgs接口Make1Argm1aDog::new;〔2〕Make2Argsm2aDog::new;〔3〕Dogdnmna。make();Dogd1m1a。make(Comet);Dogd2m2a。make(Ralph,4);}}总结方法引用在很大程度上可以理解为创建一个函数式接口的实例方法引用实际上是一种简化Lambda表达式的语法糖,它提供了一种更简洁的方式来创建一个函数式接口的实现在代码中使用方法引用时,实际上是在创建一个匿名实现类,引用方法实现并且覆盖了接口的抽象方法方法引用大多用于创建函数式接口的实现函数式接口Lambda包含类型推导Java8引入java。util。function包,解决类型推导的问题 通过函数表达式创建Interface:使用FunctionalInterface注解强制执行此函数式方法模式FunctionalInterfaceinterfaceFunctional{Stringgoodbye(Stringarg);}interfaceFunctionalNoAnn{Stringgoodbye(Stringarg);}publicclassFunctionalAnnotation{goodbyepublicStringgoodbye(Stringarg){returnGoodbye,arg!;}publicstaticvoidmain(String〔〕args){FunctionalAnnotationfanewFunctionalAnnotation();FunctionalAnnotation没有实现Functional接口,所以不能直接赋值Functionalfacfa;Incompatible?但可以通过Lambda将函数赋值给接口(类型需要匹配)Functionalffa::goodbye;FunctionalNoAnnfnafa::goodbye;FunctionalflaGoodbye,a;FunctionalNoAnnfnalaGoodbye,a;}} 以上是自己创建函数式接口的示例。 但在java。util。function包旨在创建一组完整的预定义接口,使得我们一般情况下不需再定义自己的接口。 在java。util。function的函数式接口的基本使用基本准测,如下只处理对象而非基本类型,名称则为Function,Consumer,Predicate等,参数通过泛型添加如果接收的参数是基本类型,则由名称的第一部分表示,如LongConsumer,DoubleFunction,IntPredicate等如果返回值为基本类型,则用To表示,如ToLongFunction和IntToLongFunction如果返回值类型与参数类型一致,则是一个运算符如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)如果接收的两个参数类型不同,则名称中有一个Bi基本类型 下面枚举了基于Lambda表达式的所有不同Function变体的示例:classFoo{}classBar{Foof;Bar(Foof){this。ff;}}classIBaz{inti;IBaz(inti){this。ii;}}classLBaz{longl;LBaz(longl){this。ll;}}classDBaz{doubled;DBaz(doubled){this。dd;}}publicclassFunctionVariants{根据不同参数获得对象的函数表达式staticFunctionFoo,Barf1fnewBar(f);staticIntFunctionIBazf2inewIBaz(i);staticLongFunctionLBazf3lnewLBaz(l);staticDoubleFunctionDBazf4dnewDBaz(d);根据对象类型参数,获得基本数据类型返回值的函数表达式staticToIntFunctionIBazf5ibib。i;staticToLongFunctionLBazf6lblb。l;staticToDoubleFunctionDBazf7dbdb。d;staticIntToLongFunctionf8ii;staticIntToDoubleFunctionf9ii;staticLongToIntFunctionf10l(int)l;staticLongToDoubleFunctionf11ll;staticDoubleToIntFunctionf12d(int)d;staticDoubleToLongFunctionf13d(long)d;publicstaticvoidmain(String〔〕args){applyusageexamplesBarbf1。apply(newFoo());IBazibf2。apply(11);LBazlbf3。apply(11);DBazdbf4。apply(11);applyAsusageexamplesintif5。applyAsInt(ib);longlf6。applyAsLong(lb);doubledf7。applyAsDouble(db);基本类型的相互转换longapplyAsLongf8。applyAsLong(12);doubleapplyAsDoublef9。applyAsDouble(12);intapplyAsIntf10。applyAsInt(12);doubleapplyAsDouble1f11。applyAsDouble(12);intapplyAsInt1f12。applyAsInt(13。0);longapplyAsLong1f13。applyAsLong(13。0);}} 以下是用表格整理基本类型相关的函数式接口: 函数式接口 特征 用途 方法名 FunctionT,R 接受一个参数,返回一个结果 将输入参数转换成输出结果,如数据转换或映射操作 Rapply(Tt) IntFunction 接受一个int参数,返回一个结果 将int值转换成输出结果 Rapply(intvalue) LongFunction 接受一个long参数,返回一个结果 将long值转换成输出结果 Rapply(longvalue) DoubleFunction 接受一个double参数,返回一个结果 将double值转换成输出结果 Rapply(doublevalue) ToIntFunction 接受一个参数,返回一个int结果 将输入参数转换成int输出结果 intapplyAsInt(Tvalue) ToLongFunction 接受一个参数,返回一个long结果 将输入参数转换成long输出结果 longapplyAsLong(Tvalue) ToDoubleFunction 接受一个参数,返回一个double结果 将输入参数转换成double输出结果 doubleapplyAsDouble(Tvalue) IntToLongFunction 接受一个int参数,返回一个long结果 将int值转换成long输出结果 longapplyAsLong(intvalue) IntToDoubleFunction 接受一个int参数,返回一个double结果 将int值转换成double输出结果 doubleapplyAsDouble(intvalue) LongToIntFunction 接受一个long参数,返回一个int结果 将long值转换成int输出结果 intapplyAsInt(longvalue) LongToDoubleFunction 接受一个long参数,返回一个double结果 将long值转换成double输出结果 doubleapplyAsDouble(longvalue) DoubleToIntFunction 接受一个double参数,返回一个int结果 将double值转换成int输出结果 intapplyAsInt(doublevalue) DoubleToLongFunction 接受一个double参数,返回一个long结果 将double值转换成long输出结果 longapplyAsLong(doublevalue)非基本类型 在使用函数接口时,名称无关紧要只要参数类型和返回类型相同。Java会将你的方法映射到接口方法。示例:importjava。util。function。BiConsumer;classIn1{}classIn2{}publicclassMethodConversion{staticvoidaccept(In1in1,In2in2){System。out。println(accept());}staticvoidsomeOtherName(In1in1,In2in2){System。out。println(someOtherName());}publicstaticvoidmain(String〔〕args){BiConsumerIn1,In2bic;bicMethodConversion::accept;bic。accept(newIn1(),newIn2());在使用函数接口时,名称无关紧要只要参数类型和返回类型相同。Java会将你的方法映射到接口方法。bicMethodConversion::someOtherName;bic。accept(newIn1(),newIn2());}} 输出结果:accept()someOtherName() 将方法引用应用于基于类的函数式接口(即那些不包含基本类型的函数式接口)importjava。util。Comparator;importjava。util。function。;classAA{}classBB{}classCC{}publicclassClassFunctionals{staticAAf1(){returnnewAA();}staticintf2(AAaa1,AAaa2){return1;}staticvoidf3(AAaa){}staticvoidf4(AAaa,BBbb){}staticCCf5(AAaa){returnnewCC();}staticCCf6(AAaa,BBbb){returnnewCC();}staticbooleanf7(AAaa){returntrue;}staticbooleanf8(AAaa,BBbb){returntrue;}staticAAf9(AAaa){returnnewAA();}staticAAf10(AAaa,AAbb){returnnewAA();}publicstaticvoidmain(String〔〕args){无参数,返回一个结果SuppliersClassFunctionals::f1;s。get();比较两个对象,用于排序和比较操作ComparatorcClassFunctionals::f2;c。compare(newAA(),newAA());执行操作,通常是副作用操作,不需要返回结果ConsumerconsClassFunctionals::f3;cons。accept(newAA());执行操作,通常是副作用操作,不需要返回结果,接受两个参数BiConsumerbiconsClassFunctionals::f4;bicons。accept(newAA(),newBB());将输入参数转换成输出结果,如数据转换或映射操作FunctionfClassFunctionals::f5;CCccf。apply(newAA());将两个输入参数转换成输出结果,如数据转换或映射操作BiFunctionbifClassFunctionals::f6;ccbif。apply(newAA(),newBB());接受一个参数,返回boolean值:测试参数是否满足特定条件PredicatepClassFunctionals::f7;booleanresultp。test(newAA());接受两个参数,返回boolean值,测试两个参数是否满足特定条件BiPredicatebipClassFunctionals::f8;resultbip。test(newAA(),newBB());接受一个参数,返回一个相同类型的结果,对输入执行单一操作并返回相同类型的结果,是Function的特殊情况UnaryOperatoruoClassFunctionals::f9;AAaauo。apply(newAA());接受两个相同类型的参数,返回一个相同类型的结果,将两个相同类型的值组合成一个新值,是BiFunction的特殊情况BinaryOperatorboClassFunctionals::f10;aabo。apply(newAA(),newAA());}} 以下是用表格整理的非基本类型的函数式接口: 函数式接口 特征 用途 方法名 Supplier 无参数,返回一个结果 获取值或实例,工厂模式,延迟计算 Tget() Comparator 接受两个参数,返回int值 比较两个对象,用于排序和比较操作 intcompare(To1,To2) Consumer 接受一个参数,无返回值 执行操作,通常是副作用操作,不需要返回结果 voidaccept(Tt) BiConsumerT,U 接受两个参数,无返回值 执行操作,通常是副作用操作,不需要返回结果,接受两个参数 voidaccept(Tt,Uu) FunctionT,R 接受一个参数,返回一个结果 将输入参数转换成输出结果,如数据转换或映射操作 Rapply(Tt) BiFunctionT,U,R 接受两个参数,返回一个结果 将两个输入参数转换成输出结果,如数据转换或映射操作 Rapply(Tt,Uu) Predicate 接受一个参数,返回boolean值 测试参数是否满足特定条件 booleantest(Tt) BiPredicateT,U 接受两个参数,返回boolean值 测试两个参数是否满足特定条件 booleantest(Tt,Uu) UnaryOperator 接受一个参数,返回一个相同类型的结果 对输入执行单一操作并返回相同类型的结果,是Function的特殊情况 Tapply(Tt) BinaryOperator 接受两个相同类型的参数,返回一个相同类型的结果 将两个相同类型的值组合成一个新值,是BiFunction的特殊情况 Tapply(Tt1,Tt2)多参数函数式接口 java。util。functional中的接口是有限的,如果需要3个参数函数的接口怎么办?自己创建就可以了,如下:创建处理3个参数的函数式接口FunctionalInterfacepublicinterfaceTriFunctionT,U,V,R{Rapply(Tt,Uu,Vv);} 验证如下:publicclassTriFunctionTest{staticintf(inti,longl,doubled){return99;}publicstaticvoidmain(String〔〕args){方法引用TriFunctionInteger,Long,Double,Integertf1TriFunctionTest::f;Lamdba表达式TriFunctionInteger,Long,Double,Integertf2(i,l,d)12;}}高阶函数 高阶函数(HigherorderFunction)其实很好理解,并且在函数式编程中非常常见,它有以下特点:接收一个或多个函数作为参数返回一个函数作为结果 先来看看一个函数如何返回一个函数:importjava。util。function。Function;interfaceFuncSSextendsFunctionString,String{}〔1〕使用继承,轻松创建属于自己的函数式接口publicclassProduceFunction{produce()是一个高阶函数:既函数的消费者,产生函数的函数staticFuncSSproduce(){returnss。toLowerCase();〔2〕使用Lambda表达式,可以轻松地在方法中创建和返回一个函数}publicstaticvoidmain(String〔〕args){FuncSSfuncSSproduce();System。out。println(funcSS。apply(YELLING));}} 然后再看看,如何接收一个函数作为函数的参数:classOne{}classTwo{}publicclassConsumeFunction{staticTwoconsume(FunctionOne,Twoonetwo){returnonetwo。apply(newOne());}publicstaticvoidmain(String〔〕args){Twotwoconsume(onenewTwo());}} 总之,高阶函数使代码更加简洁、灵活和可重用,常见于Stream流式编程中闭包 在Java中,闭包通常与lambda表达式和匿名内部类相关。简单来说,闭包允许在一个函数内部访问和操作其外部作用域中的变量。在Java中的闭包实际上是一个特殊的对象,它封装了一个函数及其相关的环境。这意味着闭包不仅仅是一个函数,它还携带了一个执行上下文,其中包括外部作用域中的变量。这使得闭包在访问这些变量时可以在不同的执行上下文中保持它们的值。 让我们通过一个例子来理解Java中的闭包:publicclassClosureExample{publicstaticvoidmain(String〔〕args){inta10;intb20;这是一个闭包,因为它捕获了外部作用域中的变量a和bIntBinaryOperatorclosure(x,y)xayb;intresultclosure。applyAsInt(3,4);System。out。println(Result:result);输出Result:110}} 需要注意的是,在Java中,闭包捕获的外部变量必须是final或者是有效的final(即在实际使用过程中保持不变)。这是为了防止在多线程环境中引起不可预测的行为和数据不一致。函数组合 函数组合(FunctionComposition)意为多个函数组合成新函数。它通常是函数式编程的基本组成部分。 先看Function函数组合示例代码:importjava。util。function。Function;publicclassFunctionComposition{staticFunctionString,Stringf1s{System。out。println(s);returns。replace(A,);},f2ss。substring(3),f3ss。toLowerCase(),重点:使用函数组合将多个函数组合在一起compose是先执行参数中的函数,再执行调用者andThen是先执行调用者,再执行参数中的函数f4f1。compose(f2)。andThen(f3);publicstaticvoidmain(String〔〕args){Stringsf4。apply(GOAFTERALLAMBULANCES);System。out。println(s);}} 代码示例使用了Function里的compose()和andThen(),它们的区别如下:compose是先执行参数中的函数,再执行调用者andThen是先执行调用者,再执行参数中的函数 输出结果:AFTERALLAMBULANCESfterllmbulnces 然后,再看一段Predicate的逻辑运算演示代码:publicclassPredicateComposition{staticPredicateStringp1ss。contains(bar),p2ss。length()5,p3ss。contains(foo),p4p1。negate()。and(p2)。or(p3);使用谓词组合将多个谓词组合在一起,negate是取反,and是与,or是或publicstaticvoidmain(String〔〕args){Stream。of(bar,foobar,foobaz,fongopuckey)。filter(p4)。forEach(System。out::println);}} p4通过函数组合生成一个复杂的谓词,最后应用在filter()中:negate():取反值,内容不包含barand(p2):长度小于5or(p3):或者包含f3 输出结果:foobarfoobaz 在java。util。function中常用的支持函数组合的方法,大致如下: 函数式接口 方法名 描述 FunctionT,R andThen 用于从左到右组合两个函数,即:h(x)g(f(x)) FunctionT,R compose 用于从右到左组合两个函数,即:h(x)f(g(x)) Consumer andThen 用于从左到右组合两个消费者,按顺序执行两个消费者操作 Predicate and 用于组合两个谓词函数,返回一个新的谓词函数,满足两个谓词函数的条件 Predicate or 用于组合两个谓词函数,返回一个新的谓词函数,满足其中一个谓词函数的条件 Predicate negate 用于对谓词函数取反,返回一个新的谓词函数,满足相反的条件 UnaryOperator andThen 用于从左到右组合两个一元操作符,即:h(x)g(f(x)) UnaryOperator compose 用于从右到左组合两个一元操作符,即:h(x)f(g(x)) BinaryOperator andThen 用于从左到右组合两个二元操作符,即:h(x,y)g(f(x,y))柯里化 柯里化(Currying)是函数式编程中的一种技术,它将一个接受多个参数的函数转换为一系列单参数函数。 让我们通过一个简单的Java示例来理解柯里化:publicclassCurryingAndPartials{staticStringuncurried(Stringa,Stringb){returnab;}publicstaticvoidmain(String〔〕args){柯里化的函数,它是一个接受多参数的函数FunctionString,FunctionString,Stringsumabab;System。out。println(uncurried(Hi,Ho));通过链式调用逐个传递参数FunctionString,Stringhisum。apply(Hi);System。out。println(hi。apply(Ho));FunctionString,StringsumHisum。apply(Hup);System。out。println(sumHi。apply(Ho));System。out。println(sumHi。apply(Hey));}} 输出结果:HiHoHiHoHupHoHupHey 接下来我们添加层级来柯里化一个三参数函数:importjava。util。function。Function;publicclassCurry3Args{publicstaticvoidmain(String〔〕args){柯里化函数FunctionString,FunctionString,FunctionString,Stringsumabcabc;逐个传递参数FunctionString,FunctionString,Stringhisum。apply(Hi);FunctionString,Stringhohi。apply(Ho);System。out。println(ho。apply(Hup));}} 输出结果:HiHoHup 在处理基本类型的时候,注意选择合适的函数式接口:importjava。util。function。IntFunction;importjava。util。function。IntUnaryOperator;publicclassCurriedIntAdd{publicstaticvoidmain(String〔〕args){IntFunctionIntUnaryOperatorcurriedIntAddabab;IntUnaryOperatoradd4curriedIntAdd。apply(4);System。out。println(add4。applyAsInt(5));}} 输出结果:9总结 Lambda表达式和方法引用并没有将Java转换成函数式语言,而是提供了对函数式编程的支持(Java的历史包袱太重了),这些特性满足了很大一部分的、羡慕Clojure和Scala这类更函数化语言的Java程序员。阻止了他们投奔向那些语言(或者至少让他们在投奔之前做好准备)。总之,Lambdas和方法引用是Java8中的巨大改进