Kotlin委托 & 扩展 & 高阶函数

写在前面

Kotlin现在已经是Android官方的一级开发语言了,以前就有大佬给我安利,最近刚好看open cv的c++和ndk看的头昏脑涨,反正最近也用不到,只是出于兴趣,不如换个脑子看看最近势头比较盛的Kotlin好了。在这里感谢一下猫哥对我的耐心指导,让我对Kotlin的认识更进了一步。

委托

委托有委托类和委托属性。

委托类

我在看文档的时候就感觉跟Java里的某个操作非常像……于是非常恶趣味的将代码写成了如下模式:

interface OnClickListener {
   fun click()
}
​
class MyListener : OnClickListener {
   override fun click() {
       println("do something about click event")
   }
}
​
class MainActivity(b: OnClickListener) : OnClickListener by b {
   init {
       println("we can do something about activity here")
   }
}
​
fun main(args: Array<String>) {
   val listener = MyListener()
   val activity = MainActivity(listener)
   activity.click()
}
​
// 输出:
// we can do something about activity here
// do something about click event

是的,我觉得这个类委托跟Java里设置接口回调这个操作很像。只不过一般情况下,我们会使用匿名类来实现这个接口,并在实现中写上我们的代码。而委托类在文档中的描述是:The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code.

又到了展示我辣鸡英语水平的时候了:委托模式是一种实现继承的良好方式,Kotlin原生支持不需要额外的样板代码。(意译,不完全符合字面意思)

也就是说跟我想的差不多,Kotlin语言本身提供了委托,可以让我们省了setxxxListener这种重复劳动。

委托属性

委托属性官方举了三个应用场景:

  • 延迟属性:只在第一次访问的时候初始化(计算)

  • 可观察属性:监听器得到关于这个特性变化的通知

  • 把所有属性存储在一个map中,而不是在单独的字段里

假如现在在项目里需要一个地点名称的属性,这个地点并不是每一次都需要用到,我们只在有需要的时候获取这个属性,我们就可以使用Kotlin提供的lazy来实现:

   val address: String by lazy {
       getAdd()
   }
​
   fun getAdd(): String = "北京天安门"

下面是可观察属性,Kotlin中提供了Observable委托,来看看是怎么用的:

class PropertyDelegate {

   var status: Int by Delegates.observable(0) {
       d, old, new ->
       println("属性名称:$d,以前是:$old,现在是:$new")
   }
}
​
fun main(args: Array<String>) {
   var p = PropertyDelegate()
​
   println(p.status)
   p.status = 2
}
// 输出:
// 0
// 属性名称:var PropertyDelegate.status: kotlin.Int,以前是:0,现在是:2

的确观察到了属性值的变化,上面observable(0)中的0作为status的初始化值,后面的那种写法暂时先放着,是将函数作为参数的一种使用方式。
接下来看看将属性存储在Map中:

class TestUser(val map: Map<String, Any?>) {

   val name: String by map
   val age: Int by map
}
​
fun main(args: Array<String>) {
   var user: TestUser = TestUser(mapOf(
           "name" to "test",
           "age" to 16
   ))
​
   println("username:" + user.name + ",age:" + user.age)
}
// 输出:
// username:test,age:16

上面三个都是Kotlin提供的委托,我们也可以自己实现属性委托:

class Http {

   var name: String? = null
​
   fun getNameInfoFromNetWork(): String {
       return name ?: "假装我是从网络获取的名字"
   }
​
   operator fun getValue(any: Any, property: KProperty<*>): String {
       println("打印观察:any=$any property=$property")
       return getNameInfoFromNetWork()
   }
​
   operator fun setValue(any: Any, property: KProperty<*>, s: String) {
       this.name = s
   }
}
​
class PropertyDelegate {
   var name: String by Http()
​
   val address: String by lazy {
       getAdd()
   }
​
   fun getAdd(): String = "北京天安门"
​
   var status: Int by Delegates.observable(0) {
       d, old, new ->
       println("属性名称:$d,以前是:$old,现在是:$new")
   }
​
​
}
​
fun main(args: Array<String>) {
   var p = PropertyDelegate()
   println(p.name)
   p.name = "hh"
   println(p.name)
}
// 输出:
// 打印观察:any=PropertyDelegate@2e5d6d97 property=var PropertyDelegate.name: kotlin.String
// 假装我是从网络获取的名字
// 打印观察:any=PropertyDelegate@2e5d6d97 property=var PropertyDelegate.name: kotlin.String
// hh

var声明的是可变量,需要实现set和get,而val是不可变量,只需要实现get。

简单的介绍了一下官方列举的这三个应用场景,其实感觉这三个例子并不能戳中我的痛点,因为现有的Java虽然实现起来啰嗦了点,但也并不是非常麻烦。比如可观察的属性,我只要在将属性声明为private,然后在set方法里设置一个接口回调就可以了,接口再弄个泛型,得了,所有可观察属性都能用这个接口了。至于后面两个也是同样的,但是这是语言层面的直接支持,喂你糖吃总是得心怀感激的,总好过吃翔不是。接下来就来了解一下Kotlin比较牛x的两个东西:扩展和高阶函数。

扩展 & 高阶函数

扩展是Kotlin中比较能打动我的一个特性,比如原来的Android中的Toast需要这么写:

Toast.makeText(mContext,"toast",Toast.LENGTH_SHORT).show();

说实话,刚开始还觉得敲着挺带感,慢慢的就烦了,自己简单的封装一下是这样:

class ToastUtil{
 public static void show(Context context,String text){
  Toast.makeText(context,text,Toast.LENGTH_SHORT).show();
 }
}

后来传context也传烦了,进一步封装,在Application里弄一个静态方法获取context,然后:

class ToastUtil{

 public static void show(String text){
  Toast.makeText(Application.getContext(),text,Toast.LENGTH_SHORT).show();
 }
}

那么利用Kotlin的扩展,可以怎么做呢?

fun Context.toast(text: String){
   Toast.makeText(this,text,Toast.LENGTH_SHORT).show()
}

这为Context类扩展了一个toast方法,这样以后你可以这样使用:

context.toast("hello")

如果你已经在上下文中:

toast("hello")

这可以说是十分的好用了,有了扩展,还要什么工(zi)具(xing)类(che)。扩展的语法比较简单,不多赘述。另外值得注意的是扩展并非继承,没办法复写类中的函数,用以下例子来说明一下:

class Extend {
   fun hello() {
       println("hello")
   }
}
​
fun Extend.hello(s: String) {
   println("$s hello")
}
​
fun Extend.hello() {
   println("无参 扩展 hello")
}
​
fun main(args: Array<String>) {
   val e = Extend()
   e.hello()
   e.hello("luo")
}
// 输出:
// hello
// luo hello

可以看到当参数和方法名一样时,调用的是类内部的方法并非扩展方法,当参数不一致时调用对应的方法。

高阶函数

高阶,这俩字一看就高端大气上档次,看一下文档的描述:A higher-order function is a function that takes functions as parameters, or returns a function。高阶函数一种能把函数作为参数,或者将函数作为返回值的函数。将函数作为参数,桥豆麻袋,这……这不是跟传递点击事件那玩意很像么!Java里写的是接口回调,Java8 可以用lambda,先不管Java内部是怎么实现lambda的,至少从形式上看是很像直接传了个函数进去的。那么,继续用这个点击作为实例:

class SecondActivity {
   fun clickEvent(click: () -> Unit){
       println("click event start")
       click()
   }
}
​
fun main(args: Array<String>) {
   val secondActivity = SecondActivity()
   secondActivity.clickEvent({
       println("do something about click event")
   })
}
// 输出:
// click event start
// do something about click event

首先解释一下clickEvent函数,click: () 表示函数类型, -> Unit表示返回值为空,也就是clickEvent接收一个返回值为空的click()方法。在clickEvent方法内部调用了这个传入的方法,达到了传递事件的目的。当然,如果函数是作为最后一个参数,是可以写成这样的:

secondActivity.clickEvent() {
    println("do something about click event")
}

如果没有其他参数,那么还能写成这样:

secondActivity.clickEvent {
    println("do something about click event")
}

相比Java,黑科技的味道慢慢的就出来了。光看这些可能你会觉得没啥,那么接下来的骚操作一定会让你对Kotlin的感觉提升一截。我也是在猫哥给我指导了一番之后,十分惊讶,还有这种操作?

令人窒息的操作

利用高阶函数可以接受函数参数这一点,能玩的东西可就多了去了,接下来就写几个好玩的:

debug模式配置

inline fun debugConf(code: () -> Unit) {
   if (BuildConfig.DEBUG) {
       code()
   }
}
​
   debugConf {
       // init
   }

这只是一个比较简单的应用场景,类似的还有Android中支持版本代码的编写,每一次都需要判断版本然后调用api,无疑是十分麻烦的,我们也可以利用高阶函数的特性来搞定。具体的代码就不再编写了。接下来举一个利用扩展和高阶函数特性编写的一个判断集合元素是否满足条件的函数。我们利用扩展给List扩展一个叫做myAll的方法,List调用此方法判断集合中的所有元素是否满足条件,如果所有元素都满足,返回true,反之false。

inline fun <T> List<T>.myAll(getItem: (T) -> Boolean): Boolean {
   for (element in this) {
       if (!getItem(element)) {
           return false
       }
   }
   return true
}
​
fun main(args: Array<String>) {
 val list = listOf(1, 2, 3, 4, 5, 6)
 println("myAll:" + list.myAll { it % 1 == 0 })
 println("myAll: " + list.myAll { it % 2 == 0 })

}
// 输出:
// myAll:true
// myAll: false

简单的解释一下,inline是内联函数,在编译时编译器将函数体嵌入在每一个调用处。先看最外层,一个泛型T,用来表示集合内元素类型。myAll方法返回Boolean类型,用来表示所有元素是否符合条件。而getItem方法接收类型为T的参数(List元素类型),返回一个Boolean值表示该元素是否符合条件。判断也是简单粗暴的,遍历List如果有getItem()的返回值是false,那么就返回false。

小结

我看Kotlin也就是断断续续的看了一些语法,总体看下来感觉Java开发者学习Kotlin的成本不是非常高,而且最重要的是,Kotlin兼容Java,迁移成本也非常的低。Kotlin中的很多特性,只是提供了一种语言层面上的支持,Java也不是不能实现,总得拐弯抹角,麻烦的一批。Java语法啰嗦受人诟病也不是一天两天了,不过Java发展到现在更像是一个平台而不是语言了,Java能做的事太多,语法已经不是最值得关注的事情了。不过如果有一个兼容Java而且还喂你各种语法糖的语言出现,为什么不尝试一下呢?如果我说成这样你还不尝试一下,那我只能……

强行给你安利一波了,大爷,试一下Kotlin呗~

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容