Kotlin 扩展

C#Gosu 类似, Kotlin也提供了一种,可以在不继承父类,也不使用类似装饰器这样的设计模式的情况下对指定类进行扩展。我们可以通过一种叫做扩展的特殊声明来实现他。

Kotlin 支持函数扩展和属性扩展。

扩展有什么好处?

其实很好理解。研发过程中经常会涉及到一些工具类,比如FileUtils用于操作文件,StringUtils用于操作字符串,Collecctions用于操作集合,然后在调用过程中就会显得很繁琐。比如这样:

// Java
Collections.swap(list, Collections.binarySearch(list,
Collections.max(otherList)), Collections.max(list))

静态导入包当然能简化:

// Java
swap(list, binarySearch(list, max(otherList)), max(list))

而我们实际上需要的是最好这样:

// Java
list.swap(list.binarySearch(otherList.max()), list.max())

所以在kotlin中考虑到这一点,就提供了扩展函数,可以对类的属性方法进行扩展,而又不用去改变类本身。

扩展函数

扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:

fun receiverType.functionName(params){
    body
}
  • receiverType:表示函数的接收者,也就是函数扩展的对象
  • functionName:扩展函数的名称
  • params:扩展函数的参数,可以为NULL

以下是一个实例

class Functions constructor(var name:String) {

    fun Functions.clean(){
        this.name = ""
    }

    fun main(){
        var func = Functions("name")
        func.clean()
        print(func.name)

    }

}

this关键字指代接收者对象(receiver object)(也就是调用扩展函数时, 在点号之前指定的对象实例)。

特别地

扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:

open class C

class D: C()

fun C.foo() = "c"   // 扩展函数 foo

fun D.foo() = "d"   // 扩展函数 foo

fun printFoo(c: C) {
    println(c.foo())  // 类型是 C 类
}

fun main(arg:Array<String>){
    printFoo(D())
}

实例执行输出结果为:

c

若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。

class C {
    fun foo() { println("成员函数") }
}

fun C.foo() { println("扩展函数") }

fun main(arg:Array<String>){
    var c = C()
    c.foo()
}

实例执行输出结果为:

成员函数

扩展一个空对象

在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

实例执行输出结果为:

null

扩展属性

除了函数,Kotlin 也支持属性对属性进行扩展:

val <T> List<T>.lastIndex: Int
    get() = size - 1

扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。

val Foo.bar = 1 // 错误:扩展属性不能有初始化器

Note:扩展属性只能被声明为 val。

扩展伴生对象

如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴随对象的扩展函数")
}

val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

实例执行输出结果为:

no:10
伴随对象的扩展函数

Note:Kotlin中的伴生对象用于实现函数或属性的静态调用。后续的高级用法中会介绍。

扩展的作用域

我们知道对象或变量其实都有作用域的,Kotlin中扩展也不例外。
官方文档上的原文是这样

Most of the time we define extensions on the top level, i.e. directly under packages.

To use such an extension outside its declaring package, we need to import it at the call site

即官方的推荐是让我们在顶层包定义扩展,然后导包使用。但是,这好像是用法?其实我们更急切地是想知道:

  • 是不是只能在该类中对该类进行扩展?
  • 我在一个类中定义了某个类的扩展,在其它类想使用怎么弄?

首先,第一个问题————当然不是,不然这与类本身的新增函数有啥区别.kotlin中扩展的好处就是可以在无法修改或没有必要修改.kt类的情况下,直接通过该类调用扩展函数或属性,以达到代码上的简洁。

(更多见下面扩展声明为成员)

class Functions constructor(var name:String) {

    fun Functions.clean(){
        this.name = ""
    }
    
    fun Person.study2(){
        print("start study")
    }
}

以上代码不仅对Functions类进行函数扩展,也同样对Person进行了函数扩展。

第二个问题————通过实际代码总结了以下几点:

  • 在某类中定义的扩展,无论是该类的扩展或是其它类的扩展,其作用域都仅限于该类以及该 类的内部类或子类中,在其它类或该类的嵌套类中都不可访问。
open class Functions constructor(var name:String) {

    fun Functions.clean(){
        this.name = ""
    }

    fun Person.study2(){
        print("start study")
    }

    fun main(person: Person){
        person.learn()
    }
    
     inner class A{//内部类中可以访问

        fun main(){
            var func = Functions("name")
            func.clean();

            val person = Person("name",18);
            person.study2()
        }
    }
    
     class N{

        fun main(){
            var func = Functions("name")
            func.clean();//错误,嵌套类中无法访问
        }
    }
}

在子类中访问

class Func3(name: String) : Functions(name) {

    fun main2(){
        val func = Functions("anme");
        func.clean()

        val person = Person("name",18);
        person.study2()
    }
}
  • 通用的扩展一般放在包的顶层,写在kotlin文件中(不是类,是文件)

Demo

创建ExtendMethods.kt文件(不是类)

package com.talent.kotlin.example

fun Person.learn(){
    print("learn")
}

在Main中调用

package com.talent.kotlin.example.bbb

import com.talent.kotlin.example.Person
import com.talent.kotlin.example.learn

class Main {
    
    fun main(){
        val person = Person("name",18);
        person.learn()
    }
}

扩展声明为成员

首先先要弄清楚两个概念:

  • 分发接受者
  • 扩展接受者

在一个类内部你可以为另一个类声明扩展。在这个扩展中,有个多个隐含的接受者。而我们把:

扩展方法定义所在类的实例称为分发接受者

扩展方法的目标类型的实例称为扩展接受者

如下:

class ExtendsMembers {

    fun buyApple(){

        print("buy apple")
    }

    class Temp{

        fun buyOrange(){

            print("buy orange")
        }

        fun ExtendsMembers.buyMany(){
            //在这里ExtendsMembers为扩展接受者
            //Temp为分发接受者
            buyApple()
            buyOrange()
        }
        fun caller(members: ExtendsMembers) {
           members.buyMany()//调用扩展函数
        }
    }

    fun main(args: Array<String>){
        val members = ExtendsMembers();
        val temp = Temp();
        temp.caller(members)
    }
}

输出结果为:

buy apple
buy orange

从上例中,可以清楚的看到,在扩展函数中,可以调用派发接收者的成员函数。

当调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this 语法。

class D {
    fun bar() { println("D bar") }
}

class C {
    fun bar() { println("C bar") }  // 与 D 类 的 bar 同名

    fun D.foo() {
        bar()         // 调用 D.bar(),扩展接收者优先
        this@C.bar()  // 调用 C.bar()
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

实例执行输出结果为:

D bar
C bar

以成员的形式定义的扩展函数, 可以声明为 open , 而且可以在子类中覆盖. 也就是说, 在这类扩展函数的派 发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。

open class D {
}

class D1 : D() {
}

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}


fun main(args: Array<String>) {
    C().caller(D())   // 输出 "D.foo in C"
    C1().caller(D())  // 输出 "D.foo in C1" —— 分发接收者虚拟解析
    C().caller(D1())  // 输出 "D.foo in C" —— 扩展接收者静态解析

}

即扩展的函数若在分发接收者的子类被重写,则调用分发接收者的子类方法,而扩展接收者则是依据方法的定义参数类型,是静态解析的。

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

推荐阅读更多精彩内容