Kotlin的扩展函数知识点

为什么需要扩展

一个新特性的出现必然是为了解决之前遗留的开发问题和提升目前开发效率。扩展函数也是如此。

首先来介绍下OOP:开放封闭原则

软件应该是可扩展,而不可修改的。也就是对扩展开放,对修改封闭

举个栗子: 当某个三方库的功能无法满足现有业务时需要新增功能时。最简单的做法就是直接对库源码修改,但是这样违反了开放封闭原则:对源码修改

更合理的方案是依靠扩展。Kotlin的扩展函数很显然能够优雅的解决这种问题。

扩展函数是什么

首先来看下他的使用:

fun MutableList<Int>.exchange(fromIndex:Int, toIndex:Int) { 
    val tmp = this[fromIndex]    
     this[fromIndex] = this[toIndex]     
     this[toIndex] = tmp 
 }

我们将MutableList叫做接受者(receivers),意思就是这个MutableList接受了这个函数,也就是给这个类扩展了这个函数。

Java中的this叫做调用者,对于普通函数来说就是该函数所属类的实例也就是调用者对象。由于这个函数是属于MutableList的,所以在这个方法体中this也就是指代的MutableList。 通俗的来说扩展函数体里面的this就是receivers的类型

扩展函数怎么用

根据上面定义的扩展函数栗子,来看下这个扩展函数的用法:

val list = mutableListOf(1,3,5) 
list.exchange(1,5)

这里看到扩展函数是基于对象实例来调用的,如果希望使用静态的方式调用又该如何写呢?稍后讲解

再谈扩展函数是什么

还是回到刚刚第二个话题,这次的是什么就不是简单的介绍了。之前有篇文章讲解过新技术必然离不开性能方面的考虑。因此再来讲解下他是如何实现扩展函数的,我们通过解析他的反编译字节码~~

public static final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) {
        //检查$receiver参数是否为空。receiver就是调用者
         Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 
         int tmp = ((Number)$receiver.get(fromIndex)).intValue();     
         $receiver.set(fromIndex, $receiver.get(toIndex));  
          $receiver.set(toIndex, Integer.valueOf(tmp));    
  }

可以看到该函数会变成一个静态不可重写的方法,并且receiver变成了第一个参数。扩展函数里的的this就是receiver参数。

public 修饰的静态方法也就是全局方法,任何地方都可以调用到(之后详细说)。

看来并没有什么神奇的地方只是将扩展函数变成了一个静态方法而已。所以性能方面是没有影响的

扩展函数在哪里可以被使用

这里首先说明下,扩展函数定义在不同的地方效果也是不一样的。

  • 不定义在类中,也就是类外部

可以看到上面反编译后的扩展函数就是这种类型,被static,public,final修饰的方法会有这个特征:在同一个包中是可以共享这个扩展函数的也就是可以调用到这个扩展函数。其他包里面如果也想使用这个函数就可以import这个包中的这个函数即可。

  • 定义在类中,也就是类内部

这时候诡异的事情出现了,扩展函数无法被调用。接下来看下对应的扩展函数反编译后的字节码:

public final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) {
            Intrinsics.checkParameterIsNotNull($receiver, "$receiver");         
            int tmp = ((Number)$receiver.get(fromIndex)).intValue();         
            $receiver.set(fromIndex, $receiver.get(toIndex));        
             $receiver.set(toIndex, Integer.valueOf(tmp));     
    }

可以看到失去了static关键字并且变成了外部类中的方法(和正常的方法没什么区别了),也就是其他地方调用不到了,只有该类或者该类的子类可以调用;如果失去了public关键字,那么将只有该类才能使用这个扩展函数,其子类也无法使用。

总结下,如果没有定义在类中那么该函数就是静态的大家都可以调用。如果定义在类中那么就默认属于该类和子类的普通函数,所以只有在该类和子类中使用。上面只是说了调用的地方,实际上调用还是需要使用receiver进行调用。

扩展函数的限制

前面介绍了扩展函数实现的原理并且看到了扩展函数的作用域信息,接下来分析下扩展函数在哪些场景下会被限制。

静态扩展函数

首先来回顾下普通的静态函数/变量如何定义,在Kotlin中使用伴生对象类将函数/变量定义在其中,那么该函数/变量就是静态函数/变量了。

class Son {     
    companion object {         
        //该变量为静态变量
        val age = 10    
    } 
 }

伴生类的实现可以观察反编译后的字节码,其是定义了一个Companion的静态内部类然后再该类中定义了这些静态变量和方法

和普通函数/变量一样,扩展函数也是一样的定义方式,在伴生对象中定义扩展函数:

fun Son.Companion.foo() {     
    println("age = $age") 
}

这样foo就不需要Son的实例直接可以通过Son的类名进行调用了。

这样似乎看起来没有什么问题,但是当我们需要扩展三方类的静态函数时,如果其没有用Kotlin的伴生对象指定静态方法/变量,那么该方案将无法使用,只能用实例去调用

函数优先级

有没有想过这样一种情况:就是这个类扩展的函数名之前在这个类中就已经存在了,那么调用这个方法时,会调用扩展函数还是之前类中定义好的方法。

答案是:之前类中定义的方法、 因此:成员方法优先级高于扩展函数

this的指向

当我们在类中使用扩展函数时,在扩展函数体内想要获取当前类的this,而不是默认的扩展函数的receivers的类型的时候,我们可以指定this@类名来指向外部类。

扩展函数注意点

调用者类型是运行时类型,而接受者类型是编译时类型也就是说当扩展被生命为成员函数时具体调用哪个类的扩展方法是由它的运行时类型决定,而具体调用哪个扩展方法是根据其被定义为什么类型也就是编译时可知类型。

调用者类型也就是上面说的定义在类内部的扩展函数只有类实例才可以调用,而接受者receiver类型是扩展哪个类的类型

还是java中的规则: 重载基于编译时类型,重写基于运行时类型。

所以在编写扩展函数时需要注意

  • 1.如果该扩展函数定义在类内部就是顶级函数/成员函数,不能被覆盖;(因为是基于运行时类型)
  • 2.我们无法访问其接收器的非公共属性;(本质是将其变为方法的第一个参数)
  • 3.扩展接收器总是被静态调度。(和重载一样)
  • 4.也是最重要的一点,不要滥用扩展特性,思考好合适的接受者receivers,不要什么都往context上堆;参数简化要考虑是否有副作用

总结

Kotlin的扩展函数是非常好用的,其符合OOP原则,而且还可以扩展很多函数Google的ktx库也是基于这个功能开发了很多好用的方法。

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

推荐阅读更多精彩内容