Kotlin学习——扩展(扩展函数和属性以及伴生对象)

Posted DayFight_DayUp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习——扩展(扩展函数和属性以及伴生对象)相关的知识,希望对你有一定的参考价值。

我们在学习Java中,有些类的API并不能帮助我们解决问题,所以我们不得不在现有的类库的基础之上,写一些工具类,要么继承,要么使用组合依赖,这实际上是一个繁琐的工作,代价较大。
Kotlin则不一样,它像C#和Gosu一样,为我们提供了扩展的特性。

扩展定义

Kotlin 同 C# 和 Gosu 类似,能够扩展⼀个类的新功能⽽⽆需继承该类或使⽤像装饰者这样的任何类型的设计模式。这通过叫做 扩展 的特殊声明完成。Kotlin ⽀持 扩展函数 和 扩展属性

我们得到了一些信息:
1.扩展我们可以在一些情况下避免继承和使用装饰者设计模式
2. 扩展能扩展函数,也能扩展属性

class One
    fun v()
        println("not extends function")
    

//为这个类进行函数扩展
fun One.v1()
    println("extends function")

上面的代码就是为One类扩展了一个函数,调用方式和类的成员函数一样,如下:

fun main(args: Array<String>) 
    var  one = One()
    one.v1()  //和类的成员函数一样的调用方式

我们还能为一个特性的泛型类添加扩展函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) 
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp

上面代码的this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象)
我们对任意泛型为Int的MutableList 调⽤该函数,只能是Int泛型的MutableList才能调用

fun main(args: Array<String>) 
    val l = mutableListOf(1, 2, 3)
    l.swap(0, 2) // “swap()”内部的“this”得到“l”的值

如果我们想要更多类型能调用swap函数,那么我们可以让他泛化:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) 
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp

这时候就可以针对任何类型的MutableList调用swap()函数了

扩展函数是静态解析的

扩展不能真正的修改他们所扩展的类。通过定义⼀个扩展,你并没有在⼀个类中插⼊新成员,仅仅是可以通过该类型的变量⽤点表达式去调⽤这个新函数。
分析一下上面的信息:
1. 扩展不是添加新的成员
2. 扩展只是用这个类型的点变量调用而已 :取决于这个变量声明的是什么类型的
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚⽅法。这意味着调⽤的扩展函数是由函数调⽤所在的表达式的类型来决定的,⽽不是由表达式运⾏时求值结果决定的。例如

open class C
class D: C()
fun C.foo() = "c"  //类C的扩展函数
fun D.foo() = "d"   //类D的扩展函数
fun printFoo(c: C)   //扩展函数根据函数调用所在的表达式类型决定的,c是C类型的,所以传入D类的对象,也会调用的是C类的扩展函数
    println(c.foo())
 

fun main(args: Array<String>) 
    printFoo(D()) //传入的虽然是D类的对象,但是声明的是C类的引用,所以调用的是C类扩展

如果⼀个类定义有⼀个成员函数和⼀个扩展函数,⽽这两个函数⼜有相同的接收者类型、相同的名字并且都适⽤给定的参数,这种情况总是取成员函数。
上面告诉我们:如果一个类的扩展函数和成员函数同名 同参 同返回值,那么只会调用成员函数,不会调用扩展函数

class C 
    fun foo()  println("member") 
 
fun C.foo()  println("extension") 

fun main(args: Array<String>) 
    var c = C()
    c.foo() //输出的只会是member,不会是extendsion,因为成员函数优先

但是同名 同返回值 不同参 ,就相当于函数重载

class C 
    fun foo()  println("member") 
 
fun C.foo(i: Int)  println("extension") 

fun main(args: Array<String>) 
    var c = C()
    c.foo(1) //输出的只会是extension,不会是member,因为相当于重载而已

可空接收者

我们不一定要为一个扩展函数指定一个接收者
注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调⽤,即使其值为 null,并且可以在函数体内检测 this == null ,这能让你在没有检测 null 的时候调⽤ Kotlin 中的toString():检测发⽣在扩展函数的内部

fun Any?.toString():String
    if (this==null) return "null"  //内部检测是不是为null
    return toString() //不是null就调用本来的toString()

data class User(var name:String,var age:Int) //定义一个数据类来做测试

fun main(args: Array<String>) 
    var u :User? = null
    println(u.toString())  //输出 null
    u = User("花花",11)
    println(u.toString()) //输出 User(name=花花, age=11)

扩展属性

除了扩展函数之外,还有扩展属性:

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

注意:由于扩展没有实际的将成员插⼊类中,因此对扩展属性来说幕后字段是⽆效的。这就是为什么扩展属性不能有初始化器。他们的⾏为只能由显式提供的 getters/setters 定义
分析上面的信息:
1. 扩展属性没有幕后字段,不能使用field关键字
2. 扩展属性不能直接初始化
3. 扩展属性要显示提供setter/getter,val类型要提供getter,var类型要提供setter和getter

class C 
    var list :ArrayList<Int> = ArrayList()


var C.size :Int
    get() = list.size
set(value) 
    this.size = value


var C.a =1 //编译期会报错,因为扩展属性没有初始化器

伴生对象的扩展

不仅仅扩展属性和函数,伴生对象也能扩展

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

fun MyClass.Companion.foo()  //扩展伴生对象的函数
    println("伴生对象扩展的函数")

val MyClass.Companion.table:Int //扩展伴生对象的属性
    get() = 1
fun main(args: Array<String>) 
    MyClass.foo() //调用伴生对象的函数
    println(MyClass.table)  //访问伴生对象的属性

扩展的作用域

⼤多数时候我们在顶层定义扩展,即直接在包⾥

package A_clazzAndObject.G_extendsTest

class A

fun A.say()
    println("类A的扩展函数")

如果我们要在另外的包中调用这个函数
就要导入,如下:

package A_clazzAndObject.test

import A_clazzAndObject.G_extendsTest.A
import A_clazzAndObject.G_extendsTest.say

fun main(args: Array<String>) 
    var a = A()
    a.say()

扩展声明为成员

在⼀个类内部你可以为另⼀个类声明扩展。在这样的扩展内部,有多个 隐式接收者 ⸺ 其中的对象成员可以⽆需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展⽅法调⽤所在的接收者类型的实例称为 扩展接收者
get一下上面的信息:
1. 我们不仅可以在顶层定义某一类的扩展,在类的内部也可以,而且是定义另一个类的扩展;
2. 我们在A类中定义B类的扩展,那么A是分发接收者B则是扩展接收者
3. 如果扩展方法要调用分发接受者或者扩展接者的函数,可以直接调用,不需要有限定符

class D 
    fun bar() 
        println("类D的成员方法")
    

class C 
    fun baz() 
        println("类C的成员方法")
    
    fun D.foo() 
        bar() // 调⽤ D.bar,这里可以直接调用,因为调用的是扩展接收者的方法,不要限定符
        baz() // 调⽤ C.baz,这里可以直接调用,因为调用的是分发接收者的方法,不要限定符
    
    fun caller(d: D) 
        d.foo() // 调⽤扩展函数
    

但是如果扩展接收者和分发接收者有同名 同参 同返回值的函数,那么是扩展接收者优先

class D 
    fun bar() 
        println("类D的成员方法")
    

class C 
    fun bar() 
        println("类C的成员方法")
    
    fun D.foo() 
        bar() // 调⽤的是 D.bar,扩展接收者优先
    
    fun caller(d: D) 
        d.foo() // 调⽤扩展函数
    


fun main(args: Array<String>) 
    var c = C()
    c.caller(D()) //输出  类D的成员方法

如果要调用的是分发接收者的函数,就要使用this@限定符:

class D 
    fun bar() 
        println("类D的成员方法")
    

class C 
    fun bar() 
        println("类C的成员方法")
    
    fun D.foo() 
        this@C.bar() // 调⽤的是 C.bar,因为使用了this@限定调用C类的成员函数
    
    fun caller(d: D) 
        d.foo() // 调⽤扩展函数
    


fun main(args: Array<String>) 
    var c = C()
    c.caller(D()) //输出  类C的成员方法

声明为成员的扩展可以声明为 open 并在⼦类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
上面的小编也是不太懂,但是先看代码吧

open class D  //声明一个开放的类D

class D1 : D()    //类D1继承D

open class C   //声明一个开放的类C
    open fun D.foo()   //类C中声明扩展D的函数,并且开放
        println("D.foo in C")
    
    open fun D1.foo()   //类C中声明扩展D1的函数,并且开放
        println("D1.foo in C")
    
    fun caller(d: D) 
        d.foo() // 调⽤扩展函数
    

class C1 : C() 
    override fun D.foo()   //覆盖D.foo()
        println("D.foo in C1")
    
    override fun D1.foo()  //覆盖D1.foo()
        println("D1.foo in C1")
    


fun main(args: Array<String>) 
    C().caller(D()) // 输出 "D.foo in C"  这里只看声明类型,所以只调用D().foo()
    C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析,只看声明的类型
    C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析,调用的是直接分发者的函数,直接分发者是C1

根据上面的代码看出
1. 扩展对于扩展接收者是静态解析的,声明的类型决定调用的扩展函数是哪个
2. 对于分发接收者,小编理解为,你覆盖了父类的方法,那么你就是直接分发接收者,那么调用的就是你覆盖的方法(前提是有覆盖)

结语:
小编学习这个的时候,都会加上一些理解,希望能对大家有所帮助,能为大家解决疑惑。欢迎大家在评论区评论。

以上是关于Kotlin学习——扩展(扩展函数和属性以及伴生对象)的主要内容,如果未能解决你的问题,请参考以下文章

深入kotlin- 伴生对象和扩展

深入kotlin- 伴生对象和扩展

Kotlin初探

Kotlin 常用语法篇

Kotlin 扩展函数和运算符重载[第一行代码 Kotlin 学习笔记]

Kotlin学习笔记——接口抽象类泛型扩展集合操作符与Java互操作性单例