文档管理中心

一文了解 lambda 用法与源码分析

用法


示例:最普遍的一个例子,执行一个线程

->我们发现它指向的是 Runnable接口

分析

  1. ->这个箭头是lambda表达式的关键操作符

  2. ->把表达式分成两截,前面是函数参数,后面是函数体。

  3. Thread的构造函数接收的是一个Runnable接口对象,而我们这里的用法相当于是把一个函数当做接口对象传递进去了,这点理解很关键,这正是函数式编程的含义所在。

  4. 我们注意到Runnable有个注解 @FunctionalInterface,它是jdk8才引入,它的含义是函数接口。它是lambda表达式的协议注解,这个注解非常重要,后面做源码分析会专门分析它的官方注释,到时候一目了然。

由此引发的一些案例


有参数有返回值的实例:集合排序

我们知道Collections.sort方法的第二个参数接受的是一个 Comparator<T>的对象,它的部分关键源码是这样的

如上已经去掉注释和部分其他方法。


我们可以看到sort的第二个参数是Comparator的compare方法,参数类型是T,分别是o1和o2,返回值是一个int。


疑问


  • 上面的示例我们看到接口都有个 @FunctionalInterface的注解,但是我们在实际编程中并没有加这个注解也可以实现lambda表达式,例如:



如上所示,确实不需要增加 @FunctionInterface注解就可以实现。


  • 如果在1中的示例的ITest接口中增加另外一个接口方法,我们会发现不能再用lambda表达式。


我们带着这两个疑问来进入源码解析。


源码解析


必须了解注解 @FunctionInterface

上源码:

我们说过这个注解用来规范lambda表达式的使用协议的,那么注释中都说了哪些呢?


  • 一种给interface做注解的注解类型,被定义成java语言规范。



  • 一个被它注解的接口只能有一个抽象方法,有两种例外。


第一是接口允许有实现的方法。

这种实现的方法是用default关键字来标记的(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法),例如


当然这是jdk8才引入的特性,到此我们才知道,知识是一直在变化的,我们在学校中学到interface接口不允许有实现的方法是错误的,随着时间推移,一切规范都有可能发生变化。


如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则:一个被它注解的接口只能有一个抽象方法


例如同样是Compartor接口中,它重新声明了equals方法:



这些是对如下注释的翻译和解释。



  • 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件:

  1. 这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class

  2. 这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。



  • 编译器会自动把满足function interface要求的接口自动识别为function interface,所以你才不需要对上面示例中的 ITest接口增加@FunctionInterface注解。


通过了解function interface我们能够知道怎么才能正确的创建一个function interface来做lambda表达式了。接下来的是了解java是怎么把一个函数当做一个对象作为参数使用的。


穿越:对象变身函数


让我们重新复盘一下上面最开始的实例:


我们知道在jdk8以前我们都是这样来执行的:

我们知道两者是等价的,也就是说 r 等价于 ()->System.out.print('hello world'),一个接口对象等于一个lambda表达式?那么lambda表达式肯定做了这些事情(未看任何资料,纯粹推理,有误再改正):

  1. 创建接口对象

  2. 实现接口对象

  3. 返回接口对象

关于 UnaryOperator


上篇文章(聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator)关于 UnaryOperator草草收尾,在这里给大家重新梳理一下,关于它的使用场景以及它与lambda表达式的关系。


  • 使用场景


要先理解它的作用,它是接受一个参数并返回与该类型同的值,来看一个List怎么用它的,java.util.List中的replaceAll就用它了:

我们可以看到这个方法的目的是把list中的值经过operator操作后重新返回一个新值,例如具体调用。


其中lambda表达式 s->s+'efg'就是这个operator对象,那么最终list中的值就变成了['abcefg'],由此我们可以知道它的作用就是对输入的值再加工,并返回同类型的值,怎么用就需要你自己扩展发挥了。


  • 与lambda表达式的关系?


在我看来,它跟lambda表达式的关系并不大,只是它是jdk内置的一种标准操作,类似的二元操作符 BinaryOperator它可以接受两个同类型参数,并返回同类型参数的值。


关于UnaryOperator,我们百尺竿头更进一步,深入到核心



我们看到这个function interface居然没有抽象方法,不,不是没有,我们继续看Function接口。


既然他们都被注解为 @FunctionInterface了,那么他们肯定有一个唯一的抽象方法,那就是 apply。


我们知道 ->lambda表达式它是不需要关心函数名字的,所以不管它叫什么, apply也好, apply1也好都可以,但jdk肯定要叫一个更加合理的名字,那么我们知道 s->s+'efg'中 ->调用的就是 apply方法。


而且我们注意到这里有一个 identity()的静态方法,它返回一个Function对象,它其实跟lambda表达式关系也不大,它的作用是返回当前function所要表达的lambda含义。相当于创建了一个自身对象。


Function算是lambda的一种扩展应用,这个Function的的作用,是 Representsafunctionthat accepts one argumentandproduces a result.意思是接受一个参数,并产生(返回)一个结果(类型可不同)。

类似的还有很多Function,都在包java.util.Function中。



你也可以创建自己的Function,它是用来表达操作是怎样的。如传入的参数是什么,返回的是什么。


其实你只要明白它抽象的是操作就可以了。


到此就知道,原来UnaryOperator没啥神秘的,jdk把这些操作放在java.util.function中也正说明了它是一个工具类,是为了提取重复代码,让它可以重用,毕竟需要用到这样的操作的地方太多了,提取是有必要的。


本文作者: cmlanche

本文链接: http://www.cmlanche.com/2018/07/22/lambda用法与源码分析/

转载来源:cmlanche.com

点赞
收藏
回复
1
分享
举报
浏览175 编辑于2019-11-25 23:43未知归属地
全部评论
最多点赞
最新发布
最早发布
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。