Kotlin是基于JVM衍生出来的新一代通用编程语言,它的目标是简洁,可读和高效,这里的高效并不是代码的运行效率高,而是说项目的开发效率高。Kotlin有太多的小巧的新特性(在Java眼中就是语法糖),比如在Kotlin中有几个作用和用法都非常接近的函数apply/with/run/let/also,它们的正统名字是作用域函数(Scope functions),今天就来学习一下这些函数的使用方法和具体区别。
Java是面向对象的王牌语言,它的特点是严谨和教条,Java写出来的代码学过Java的人大多都看得懂,所以规模以上的项目现在基本上都用Java,这对维护是有好处的。但Kotlin不一样,它有非常多的特性,融合了众多编程语言的特点,同样一件事情,可能有无数种写法,虽然号称是用标准Kotlin语言实现的,但是即使学过Kotin的人也看不懂。比如虽然你学会了Function,Object和lambda,以及像inline function和extension,但是如果用apply和with写几段方法,你就看不懂了,这就导致了Kotlin虽然易于上手,但是要想学透和提高曲线 就会陡峭许多。
到底是个啥
先来看一下Scope function到底是什么,它们的作用是在一个对象上执行一段代码,我们来看一个简单的例子:有一个类是Person,它有一些属性和方法,我们想对它的一个对象进行操作,通常会这样做:
1 2 3 4 5 |
|
但使用scope function,我们可以这样做:
1 2 3 4 5 6 7 8 9 |
|
这两段代码的输出是完全一样的,但是第二段明显要简洁很多这就是scope function的作用,仔细看apply后面的lambda块,它是一个scope,犹如在对象的类定义之中,在这个代码块中可以直接引用对象的方法,而不是像常规的那样使用对象的引用。
注意:如果不是很尾部lambda的同学可以先行参考另外一篇文章,以加强理解。
理解Scope
作用域也可以理解为一个代码块的上下文,也就是说在一个代码中,可以直接使用的东西,环境变量之于进程,系统框架为应用准备的基础对象,都可以视为一种scope。最为明显的就是类的定义,在类中,我们可以引用this指针来代表当前对象super指针来代表基类,这也是一种scope。lambda捕获的闭包也是一种scope。
Kotlin的scope functions就是把某一个对象当作代码块的scope,代码块中的代码可以方便的使用这个对象。
Scope funtions的作用
如同开头讨论的,能用scope function写出来的东西,用常规方式也一样可以做到,那到底图个啥呢?用scope function的方式代码变得更加的简洁和紧凑,我们把针对某一对象的密集操作集中在一起放入一个代码块中,会更加的内聚和紧凑,易于扩展和维护。但也要注意不能滥用,代码块中只应该写与对象相关的操作,与scope对象不相干的事情是绝对不应该放入其中的。
Scope functions
主要有6个,它们的应用主体都是一个对象,也就是要在一个对象上面调用这些函数,然后提供一个代码块(lambda):
Scope Function | Object reference | Return value | Description |
---|---|---|---|
let | it | lambda result | Extension function |
run | this | lambda result | Extension function |
run | _ | lambda result | No object in the scope |
with | this | lambda result | Take the object as an argument |
apply | this | context object | Extension function |
also | it | context object | Extension function |
它们的区别和如何选用
with不是一个extension函数
其他几个都是extension函数,所以with一定要把scope object作为参数传入。
scope对象的引用方式
对于scope function来说scope对象都会作为一个context object,可以在lambda块中使用,有些是作为this指针,有些是作为lambda的默认参数名字也即it指针,但它们都指向context object,本质上是没有区别的只是指针的名字一个是this一个是it。但是,跟类的定义scope是一样的,this指针是可以省略的,但如果it作为参数,则是不能省略的,具体来说,比如说,用apply时,代码块中是this指针,那么可以直接这样写:
1 2 3 4 5 |
|
当然 你也可以显式的把this写出来,this.moveTo(“London”),但这就麻烦多了,何必呢。所以apply最合适的场景是对对象本身的操作,如赋值和修改属性。
但如果是用also,就必须用it了,这个不能省,因为它是对scope对象的引用:
1 2 3 |
|
所以,also最适合的不是对对象本身的操作,而是一些与对象相关的副作用,如打印日志等。
返回值不同
这坨Scope functions是一个函数,它是有返回值的,这个返回是不一样的,apply/also返回的是context object,其他几个则是返回lambda中的返回值也就是lambda的最后一个表达 式或者lambda中显式的return语句。
所以,如果是想继续使用scope object,那么就要用apply/also,如果想得到某个其他值就要用let/run/with,即使说不在乎函数的返回值时,这时也推荐使用also,因为假如后续想继续添加其他操作时,可以直接在后面链接上其他的scope function。其他返回值的let/run/with一般用在一组操作的确定性的终点上面,比如统计均值,那最后的均值计算可以用run,比如文件操作,读写都可以用with。
注意事项
任何技术和工具要深刻理解它们的应用范围和使用场景以避免滥用,要用到恰到好处才能发挥最大的价值。对于一些非必须的东西,更是如此。
Scope functions是应用于对象上面的,所以前提是当你需要对一个对象进行一些操作时,才可以使用scope functions,具体选择哪一个参考 上面一节的讨论。另外,就是放入代码块中的操作必须全部是scope对象相关的才可以。一个scope function中只能是一组相关的操作,不同组的操作要启用不同的scope functions。比如说网络请求response的处理,可以分为服务器状态码和返回实体的检测,转成具体数据,打印日志这么三个scope functions,而不是全放进一个里面。
总而言之,要视具体的需求和场景,并基于场景选择合适的scope function,切忌过度使用。