Kotlin学习笔记11——拓展

前言

上一篇,我们学习了Kotlin中的继承,今天继续来学习Kotlin中的拓展。

Kotlin 扩展

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

扩展函数

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

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

以下实例是为 User 类添加拓展函数 :

class User(var name:String)

/**扩展函数**/
fun User.Print(){
    
    
    print("用户名 $name")
}

fun main(arg:Array<String>){
    
    
    var user = User("Runoob")
    user.Print()
    //输出结果:用户名 Runoob
}

再比如给activity添加一个showToast方法:

fun Activity.showToast(msg: String){
    
    
    Toast.makeText(this,msg,Toast.LENGTH_LONG).show()
}

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

showToast("xxx")

扩展函数作用域

扩展函数写在哪都可以,但写的位置不同,作用域就也不同。所谓作用域就是说你能在哪些地方调用到它。

普通作用域

通常我们说的这类拓展函数是在类中定义,这类拓展函数只能再当前类中调用但必须使用那个前缀类的对象来调用它。上面为 User 类添加拓展函数就是这类。

顶层作用域

顶层作用域也是拓展函数最简单的写法,让它不属于任何类,这样你就能在任何类里使用它。这也和成员函数的作用域很像——哪里能用到这个类,哪里就能用到类里的这个函数:

package com.example.kotlindemo

import android.app.Activity
import android.widget.Toast

fun Activity.showToast(msg: String){
    
    
    Toast.makeText(this,msg,Toast.LENGTH_LONG).show()
}

有一点要注意了:这个函数属于谁?属于函数名左边的类吗?并不是的,它是个 Top-level Function,它谁也不属于,或者说它只属于它所在的 package。那它为什么可以被这个类的对象调用呢?——因为它在函数名的左边呀!在 Kotlin 里,当你给声明的函数名左边加上一个类名的时候,表示你要给这个函数限定一个 Receiver——直译的话叫接收者,其实也就是哪个类的对象可以调用这个函数。虽然说你是个 Top-level Function,不属于任何类——确切地说是,不是任何一个类的成员函数——但我要限制只有通过某个类的对象才能调用你。这就是扩展函数的本质。

指向扩展函数的引用

函数是可以使用双冒号被指向,示例:

//返回的是函数引用
Int::toFloat

其实指向的并不是函数本身,而是和函数等价的一个对象,这也是为什么你可以对这个引用调用 invoke(),却不能对函数本身调用:

(Int::toFloat)(1) // 等价于 1.toFloat()
Int::toFloat.invoke(1) // 等价于 1.toFloat()
1.toFloat.invoke() // 报错

但是为了简单起见,我们通常可以把这个「指向和函数等价的对象的引用」称作是「指向这个函数的引用」,这个问题不大。那么我们基于这个叫法继续说。普通函数可以被指向,扩展函数同样也是可以被指向的:

fun Dog.eat(){
    
    
    println("eat food")
}

Dog :: eatFood

不过如果这个扩展函数不是 Top-Level 的,也就是说如果它是某个类的成员函数,它就不能被引用了:

class Extensions {
    
    
  fun String.method1(i: Int) {
    
    
    ...
  }
  ...
  String::method1 // 报错
}

为什么?你想啊,一个成员函数怎么引用:类名加双冒号加函数名对吧?扩展函数呢?也是类名加双冒号加函数名对吧?只不过这次是 Receiver 的类名。那成员扩展函数呢?还用类名加双冒号加函数名呗?但是……用谁的类名?是这个函数所属的类名,还是它的 Receiver 的类名?这是有歧义的,所以 Kotlin 就干脆不许我们引用既是成员函数又是扩展函数的函数了,一了百了。同样,跟普通函数的引用一样,扩展函数的引用也可以被调用,直接调用或者用 invoke() 都可以,不过要记得把 Receiver 也就是接收者或者说调用者填成第一个参数:

(String::method1)("rengwuxian", 1)
String::method1.invoke("rengwuxian", 1)

// 以上两句都等价于:
"rengwuxian".method1(1)

把扩展函数的引用赋值给变量

扩展函数的引用也可以赋值给变量:

val a: String.(Int) -> Unit = String::method1

然后你再拿着这个变量去调用,或者再次传递给别的变量,都是可以的:

"rengwuxian".a(1)
a("rengwuxian", 1)
a.invoke("rengwuxian", 1)

扩展函数是静态解析的

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

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

注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。

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

扩展属性只能被声明为 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
伴随对象的扩展函数

伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。

对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:

  • 类内扩展的伴生对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
  • 当类内扩展的伴生对象函数和类外扩展的伴生对象同名时,类内的其它函数优先引用类内扩展的伴生对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
  • 类内扩展的伴生对象函数只能被类内的函数引用,不能被类外的函数和伴生对象内的函数引用;
  • 类外扩展的伴生对象函数可以被伴生对象内的函数引用。

代码示例:

class MyClass {
    
    
    companion object {
    
    
        val myClassField1: Int = 1
        var myClassField2 = "this is myClassField2"
        fun companionFun1() {
    
    
            println("this is 1st companion function.")
            foo()
        }
        fun companionFun2() {
    
    
            println("this is 2st companion function.")
            companionFun1()
        }
    }
    fun MyClass.Companion.foo() {
    
    
        println("伴随对象的扩展函数(内部)")
    }
    fun test2() {
    
    
        MyClass.foo()
    }
    init {
    
    
        test2()
    }
}
val MyClass.Companion.no: Int
    get() = 10
fun MyClass.Companion.foo() {
    
    
    println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {
    
    
    println("no:${
      
      MyClass.no}")
    println("field1:${
      
      MyClass.myClassField1}")
    println("field2:${
      
      MyClass.myClassField2}")
    MyClass.foo()
    MyClass.companionFun2()
}

运行结果:

no:10
field1:1
field2:this is myClassField2
foo 伴随对象外部扩展函数
this is 2st companion function.
this is 1st companion function.
foo 伴随对象外部扩展函数

尾巴

今天的学习笔记就先到这里了,下一篇我们将学习Kotlin中的数据类和密封类
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

猜你喜欢

转载自blog.csdn.net/abs625/article/details/106915586
今日推荐