java8学习之Lambda表达式继续探讨&函数式接口Function等详解

对于上次【http://www.cnblogs.com/webor2006/p/8186039.html】已经初步引入的Java8中Stream流的概念,其中使用了map的操作,它需要接受一个Function这样的函数式接口,回顾一下:

 

而这次专门对Function这个函数式接口进行进一步学习,因为这个函数式接口是比较重要的,先查看一下该接口的javadoc:

另外还有三个方法,两个默认方法、一个静态方法:

乍一看这些具体实现的方法貌似写得挺复杂的,各种泛形,所以前期会仔细的一个个去学习,待熟悉之后,对于之后再看到类似的实现理解起来就比较容易了,得循序渐进~

下面来实例化一个Function,还是用之前转大写的那个方式来创建:

其中有个注意的地方在上次已经说明了,这里再强调一下:

其实也很好理解,因为toUpperCase是String类中的实例方法,要想调用这个方法必定是String的实例对象去调用,所以这里可以总结一个套路:如果说是通过类这个类型后面跟着"::"之后引用的是一个类的实例方法(如:String::toUpperCase),那么,它所对应Lambda表达式的第一个参数就是调用这个方法的那个对象。当然这是未来要学习的方法引用(Method References)的创建形式之一,这里也先有个印象既可。

下面引用一个新的字符串排序的例子,将一个集合中的字符串按钮倒序排序输出,这里先用传统的方式来实现:

查看一下Comparator源码:

所以,这里改用Lambda表达式来编写:

这时看一下提示:

按照提示的做法如下:

但是由于这种建议的方式脱离了我们想要学习的Lambda表达式,所以还是将其还原成Lambda方式,置灰忽略~

其实对于目前写的这种Lambda表达式是可以简单化的,看:

如下:

还可以继续简化,对于Lambda表达式的语句体目前就一句话,可以简化为:

那上面的这些演变规则是怎么样的呢?这里继续对Lambda表达式进行一个文字总结,看完之后就晓得上面的演变过程啦:

Lambda表达式作用:

  • 传递行为,而不仅仅是传值
  • 提升抽象层次
  • API重用性更好
  • 更加灵活

Java Lambda基础语法:

  • Java中的Lambda表达式基本语法:(argument) -> {body}
    比如说:
      (arg1, arg2...) -> {body}
      (type1 arg1, type2 arg2...) -> {body}

Java Lambda示例:

  • (int a, int b) -> {return a + b;}
    接收两个整型参数,并返回一个整数。
  • () -> System.out.println("hello world");
    不接收参数,也不返回值,打印hello world。
  • (String s) -> {System.out.println(s);}
    接收一个字符串,并打印出该字符串,不返回值。
  • () -> 42
    无参数,返回42。
  • () -> {return 3.1415};
    无参数,返回3.1415。

Java Lambda结构:

  • 一个Lambda表达式可以有零个或多个参数。
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a) 与(a)效果相同。
  • 所有参数需要包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42。
  • 当只有一个参数,且其类型可推导时,圆括号 () 可以省略。例如:a -> return a * a。
  • Lambda表达式的主体可包含零条或多条语句。
  • 如果Lambda表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致。例如:s -> System.out.println(s)
  • 如果Lambda表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。

还有一个细节需要说明一下,看代码说话:

而如果加了return的话:

其实智能的IDE对于这种只有一条语句的完整写法也会提示我们写成expression的方式,如下:

好了,关于Lambda表达式的总结到这,已经对其了解得比较细了,下面还是回到这次要学习的主题上来。

Function接口:

下面开始编写代码来用一用这个接口,首先先利用Function这个接口来定义一个方法:

这方法定议了怎么使用它呢?看着挺奇怪的,甭急,下面先来看下如何使用它,当使用之后就能体会其意义了:

那为啥结果是2呢?分析一下:

程序理解上貌似不难,但是貌似还木有看出使用Function之后的好处,下面继续再多次调用下:

下面再来写一个返回String的Function,如下:

那举这几个形式的例子要说明啥呢?有点懵,其实重点是为了说明Lambda表达式传递的并非是值,而是一种形为,那如果不用Lambda表达式的方式,改用传统的方式那代码会变成什么样子呢?

那首先得定义三个方法如下:

然后再调用:

通过一对比,有木有看出来端倪,很明显光看compute这个方法,是无法知道具体的行为的,是加、减、乘、除,都不得而知,而具体的行为是在调用的时候才传递的,而传统的方式compute2是提前就定义好了行为之后,之后直接调用,这就是函数式编程跟面向对象编程的一个很大的区别,举这个例子就是用来体会这个区别的。

另外还有一个小细节提一下,就是由于目前咱们传的行为代码都只有一行,直接传木有啥问题~~但是如果这个行为代码量非常大,那可以将这个行为单独定义好,如下:

这样的话就不至于在行为比较复杂的时候显得代码过于零乱,当然具体如何写得根据实际情况。

高阶函数:

它的定义是:如果一个函数接收一个函数作为参数,或者返回一个函数作为返回值,那么该函数就叫做高阶函数。所以说像刚才我们定义的这个函数既可高阶函数:

这个在之后还会不断遇到的。


Function接口:

上次中已经使用了Function的apply()方法,但是在这个接口中还存在三个具体实现的方法,如下:

下面来仔细的将剩下的方法学习一下:

compose():

首先来读一下该方法的javadoc的描述:

一路读下来貌似还是有点懵,感觉好绕,下面再来看一下它的具体实现,这样可以就容易理解一些:

说白了,这个方法就是可以组合多个Function,由于该函数最终还是返回的Function,也就是说可以不断的进行多次的compose,了解了方法的说明之后,下面用代码来使用一下它:

先根据咱们读文档上的描述来分析一下:

所以,该程序的结果输出应该是12,下面编译运行一下:

如我们所预期~

andThen():

理解了compose()方法,对于这个方法来说就比较容易理解了,跟compose()恰恰相反,也就是先执行当前函数,然后再执行andThen()方法传参的函数,下面也来读一下它的javaDoc:

下面再来看一下它的具体实现:

下面再来用代码来使用下,其实是比较好理解的:

在运行之前,咱们也来先分析一下结果:

编译运行:

identity():

对于这个接口非常简单,看一下javadoc:

所以这里就不演示了。

BiFuction接口:

对于Function函数式接口而言,只有一个输入参数,那如果想传两个参数呢?这时就可以用BiFunction接口啦,其中的Bi是Bidirectional【双向】的简写,下面看一下它的javadoc:

那怎么使用它呢?试想一下传统方式要实现两数的四则运算会如何去写呢?定义好加减乘除方法,然后分别去调用既可,伪码如下:

那试想一下,是不是对于两数四则运算的场景刚好满足BiFunction的特性,传递两个参数,最后返回一个值,只是说可能具体的行为需要调用者来传递,这不正好是使用Lambda表达式的用途所在么?所以下面用新的写法来实现两数的四则运算:

编译运行:

再一次体现出Lambda表达式传递的是行为,而非值。

另外再来看一下它有默认方法相比Function接口而言有啥区别:

关于为什么在BiFunction中少了compose()方法,这个在之后会进行说明,先看一下具体实现:

由于该方法的javadoc跟Function的andThen说明一样,所以就不细读了,下面直接应用一下:

同样的在运行之前先来分析一下结果:

编译运行:

思考:

①、BiFunction相比Function为啥没有compose()方法,而只有andThen()方法呢?

这里用一个假设法来进行解释,假设BiFunction中有compose()方法,根据Function的compose()方法来看,很明显是需要先执行before函数的,如下:

当然截图的这是Function.compose()方法原型,要是BiFunction也有compose()方法的话,那这个before参数应该也是BiFunction类型的,但是无所谓,问题的重点是:这两个函数最终执行只能返回一个结果,因为一个方法只能有一个返回值,而执行完before函数之后,接着就要应用到当前的函数上了,还是以Function原型举例:

而咱们讨论的是BiFunction,那应用它时是需要两个输入参数的,但是before执行完之后只得到了一个结果,那不互相矛盾了么?所以这就是为啥在BiFunction中木有提供compose()方法的原因啦。

②、 为啥BiFunction的andThen()方法不是BiFunction类型,而是Function类型呢?

这个问题在前面也已经抛出来了,其实如果理解了第一个思考问题那对于这个问题比较容易理解,为啥呢?看下具体实现:

而接着要将这个唯一的返回值要应用到after参数上,因为只有一个参数,所以也只能是用Function,刚好符合Function只有一个参数的语法要求,不可能是用BiFunction。


Supplier接口:

继续学习一个新的函数式接口--Supplier,它的中文意思为供应商、提供者,下面看一下它的javadoc:

而具体的方法也是相当的简单,就是不接受任何参数,返回一个结果:

对它有了大概的了解之后,下面用代码来使用一下它:

貌似这函数的使用相当简单呀,但是简单并非是它的用处少,那它到底可以应用在现实中的什么场合之下呢?工厂就是其一,因为工厂有时里面是不接收参数的,直接从工厂里面返回一个结果,当然如果工厂里面要接收参数且返回结果那Function函数式接口就可以派上用场了,下面还是看一下这种Supplier的场景:

首先定义一个实体:

接着生成Student对象,然后再拿对象的属性,通常的做法如下:

当然这不是我们想要去做的,而是想通过Supplire函数式接口来生成Student对象并去拿属性,具体怎么做呢?

当然上面的代码还可以更加精简,可以采用构造方法引用【关于方法引用这个到时会仔细学习的,先有个印象既可】来修改:

那为啥"Student::new"就能返回Supplier的实例呢?因为Student的构造方法是不接收参数但是返回Student对象,这不就正好符合Supplier的约定么?下面看一个细节:

上面的效果要归功于IDE的智能,在点击new时自动就跳到了Student的定义,而点"::"时跳到了Supplier的定义。也进一步说明这个方法引用方式可以生成Supplier接口的实例,目前Student类中只有一个默认的构造方法,那如果再加一个非默认的构造方法会有什么情况发生呢?

发现咱们的程序依然可以正常运行,这还得归功于编译器的智能,怎么个智能法呢?

所以就会到Student类中找一个不带参数的但是能返回Student的构造方法,发现不就是默认的构造方法么,如果咱们将默认构造方法注释掉,看有啥反应:

立马就报错了,说明确实如咱们所分析的那样。

当学习到这,基本上就已经将Java8所涉及的重要的函数式接口就已经学习得差不多了,通过合理的搭配这些不同的函数式接口可以帮助我们完成现实中的很多很多的事情。

BinaryOperator接口:

在函数式接口的学习暂且告一段落之前,最后再来学一个新的函数式接口扩展一下,其实也不是完全的新函数,而是继承咱们之前学习的BiFunction来实现的一个特例,下面看下javadoc对它的描述:

从这句话的描述其实就能感知到:对于BinaryOperator是接受两个参数,而这两个参数的类型是一样的,其返回结果也是跟参数是相同的类型。

但是在BinaryOperator这个接口中貌似也木有找到这个方法呀:

很显然这个方法是从BiFunction继承过来滴,另外再看一下它的泛型声明:

那这个接口存在有啥价值么?还记得之前用BiFunction对两个整数进行了四则运算如下:

但是~~有木有发现两整数四则运算之后的结果是不是也是整数类型,那不正符合BinaryOperator函数式接口的特性么,有了它可以让代码编写更加简洁,下面具体看一下:

比较容易理解,对于这个接口它还有两个静态的方法,下面也来看一下:

看下它的具体实现:

等于是生成一个返回两个数中较少的一个函数式接口,有了它之后再去应用到两数上,并且咱们指定一个比较器之后就可以按咱们自己的规则来返回两数中较小的那个数字啦,下面看下如何来应用它,这里以从两个字串中找出最小的为例:

接下来调用一下:

那接下来更改一下比较规则:字符串首字母的ASCII排前面的则为较小的,如“h”在ASCII表中是排在"w"之前的,所以应该返回"hello123"啦,如下:

 对于它的另一个静态方法maxBy就完全不用读就能懂啦,如下:

下面直接应用将其改为maxBy,可想而知其返回就刚好相反啦:

函数式接口总结:

至此,Java8函数式接口的学习就告一段落了,其函数式接口传递的是行为这个一定得要好好体会,对于Java8中的函数式接口全部位于java.util.function这个包中:

展开包看一下所有的函数式接口:

复制代码
BiConsumer
BiFunction
BinaryOperator
BiPredicate
BooleanSupplier
Consumer
DoubleBinaryOperator
DoubleConsumer
DoubleFunction
DoublePredicate
DoubleSupplier
DoubleToIntFunction
DoubleToLongFunction
DoubleUnaryOperator
Function
IntBinaryOperator
IntConsumer
IntFunction
IntPredicate
IntSupplier
IntToDoubleFunction
IntToLongFunction
IntUnaryOperator
LongBinaryOperator
LongConsumer
LongFunction
LongPredicate
LongSupplier
LongToDoubleFunction
LongToIntFunction
LongUnaryOperator
ObjDoubleConsumer
ObjIntConsumer
ObjLongConsumer
Predicate
Supplier
ToDoubleBiFunction
ToDoubleFunction
ToIntBiFunction
ToIntFunction
ToLongBiFunction
ToLongFunction
UnaryOperator
复制代码

其中标红的为咱们已经学习过的,那还有这么多木有学习呀,那怎么办~其实咱们学的是非常之核心的函数式接口,其它木有学习到的函数式接口都是可以触类旁通的,不信下面举几个未学习过的,一看就懂:

Consumer咱们已经学习过了,但是BiConsumer木有学习,但是没关系,可以从Consumer的用法推导出BiConSumer,先回忆下Comsumer:

根据Function跟BiFunction的规则,那BiConsumer很显然就是有两个输入参数无返回值嘛,如下:

它的使用还用学么,依葫芦画瓢~

同样的我们上次刚学习了Predicate,回忆下:

那BiPredicate呢?还需要动脑去学习它的使用方法么?

下面再看:

这么多,其实也就是换了个数据类型而已,拿其中的一个举例:

其它的类型就以此类推,基本上是看一眼就能知道怎么使用,再往下看看:

也拿其中的一个举例:

所以说只学扎实了几个重要的函数式接口就能将所有Java8的函数式接口都学通,当然之后还会大量的对函数式接口进行使用,在未来学习Stream的时候其实就是对Lambda表达式的一个彻底的使用,函数式接口的学习就先到此,下次继续深入Java8其它的一些东东~

参考链接:
java8学习之Supplier与函数式接口总结
java8学习之Function与BiFunction函数式接口详解
java8学习之Lambda表达式继续探讨&Function接口详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值