Android Kotlin

Kotlin基础-扩展

2023-04-10  本文已影响0人  独自闯天涯的码农

一、定义

Kotlin 在不修改类 / 不继承类的情况下,向一个类添加新函数或者新属性,更符合开闭原则。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

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

扩展的本质:扩展函数是定义在类外部的静态函数,函数的第一个参数是接收者类型的对象。这意味着调用扩展时不会创建适配对象或者任何运行时的额外消耗。

二、扩展函数

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
2、若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
3、扩展一个空对象

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

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

三、扩展属性

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

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

扩展属性只能被声明为 val

四、伴生对象的扩展

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

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

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

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

伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。
对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:
(1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
(2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
(3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;
(4)类外扩展的伴随对象函数可以被伴随对象内的函数引用;

五、扩展的作用域

通常扩展函数或属性定义在顶级包下;
要使用所定义包之外的一个扩展, 通过import导入扩展的函数名进行使用;

六、扩展声明为成员

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" —— 扩展接收者静态解析
}


//实例执行输出结果为:
//D.foo in C
//D.foo in C1
//D.foo in C

七、内置扩展函数

扩展函数是 Kotlin 用于简化一些代码的书写产生的,其中有 五个函数

1、let 函数

  1. 在函数块内可以通过 it 指代该对象。
  2. 返回值为函数块的最后一行或指定return表达式。
//一般写法
val result = "hello".let {
  println(it.length)
  1000
}

使用场景:
1、使用let函数处理需要针对一个可null的对象统一做判空处理;
2、又或者是需要去明确一个变量所处特定的作用域范围内可以使用;

2、with 函数

  1. 前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。
  2. 返回值为函数块的最后一行或指定return表达式。
//一般写法
var result = with(Person()) {
    println(this.name + this.age)
    1000
}

使用场景:
1、适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上

3、run 函数

  1. 实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回。
  2. 返回值为最后一行的值或者指定的return的表达式。
var result = person.run {
    println("$name + $age")
    1000
}

使用场景:
1、适用于let,with函数任何场景。
因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理

4、apply 函数

  1. 从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样
  2. 返回值是传入对象的本身
val person = Person().apply {
    name = "liming"
    age = 50
}

使用场景:
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。正是基于这一点差异它的适用场景稍微与run函数有点不一样。
apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。特别是在我们开发中会有一些数据model向View model转化实例化的过程中需要用到

5、also 函数

  1. also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。
  2. 返回值是传入对象的本身。
val result = "hello".also {
    println(it.length)
}

使用场景:
适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用。

区别

方法名 是否有it 是否有this return类型
let × 最后一句
also × this
apply × this
run × 最后一句
with × 最后一句
为什么run、with、apply用this,also 和 let 用it?
上一篇 下一篇

猜你喜欢

热点阅读