想一想,当我们使用Java开发Android的时候有没有遇到过这种场景:假设我们需要给某个类添加一个通用方法的时候,是不是必须继承这个类,然后去自定义我们的方法。例如我们要给TextView添加一个设置text的方法,我们就必须:

public class SuperTextView extends TextView {

    public SuperTextView(Context context) {
        super(context);
    }

    public SuperTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SuperTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public SuperTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * 自定义一个text方法
     */
    public void text(String text) {
        if (!text.isEmpty()) {
            this.setText(text);
        }
    }
}

是不是很麻烦,先得写一堆构造函数(其实我们并没有对构造函数做特别的处理,所以在这里是不需要的),然后才可以写我们的方法,然而在Kotlin中,我们只需要:在一个文件中声明一个函数:

fun TextView.text(text:String){
    this.text = text
}

然后我们就可以对任意的TextView使用这个函数,其中放置在函数名称之前的类或者接口的名字就是我们期望去扩展的类或接口,在这里 即是TextView,叫做接收器类型,而调用的扩展函数的值,即这个this叫做接收器对象

扩展函数的概念:一个可以作为一个类成员进行调用的函数,但是定义在这个类的外部。

在扩展函数中,我们可以直接访问所扩展类的函数和属性,但这并不意味着我们可以打破封装,跟定义在类中的方法不同,扩展函数不允许访问私有或保护访问属性的类成员。

导入扩展函数

当定义一个扩展函数的时候,它并不会自动的在整个项目中变为可用,需要被我们像其他函数那样导入,这在一定程度上也可以避免命名冲突,在Kotlin中,允许Kotlin允许使用用于类的同样单独语法来导入单独的函数:

import package com.example.admin.kotlindemo.kotlin.text
textview.text("LBJFan")

当然,使用通配符( * )导入也是可以的:

import package com.example.admin.kotlindemo.kotlin.*

同时,kotlin页提供了as 关键词来该改变我们所导入的类或者函数的名字:
例如:在这个类中我们 将TextView的扩展函数text命名为setTextValue

import com.example.admin.kotlindemo.kotlin.text as setTextValue
textview.setTextValue("LBJFan")

当在不同的包中有多个同名的函数并且你想在同一个文件中使用它们时,在导入中改变一个名字是非常有用的,对于常规的类和函数,在这种情况下有另外的选择:可以使用完全有效的名字来有用类或者函数。对于扩展函数,这个语法要求你使用缩写名字,因此
一个导入声明中的as关键词是解决冲突的唯一方法。

扩展函数的调用

调用一个扩展函数并没有涉及适配器对象的创建或者任何其他运行时开销。在底层方面,一个扩展函数是一个接受接收器对象作为第一个参数的静态方法。 这使得从Java中使用扩展函数变得非常容易:调用静态方法并传递接收器对象实例

例如上面的调用:

textview.setTextValue("LBJFan")//textviw是一个TextView对象

我们都知道Jvm只能在类中执行代码,而Kotlin也是通过JVM运行的,所以当我们在kt文件中声明一个函数被调用时,编译器是如何工作的呢?假设text扩展函数声明的文件叫做TextUtils.kt,它被编译成Java类代码以后:

package com.example.admin.kotlindemo.kotlin;
public class TextUtils {//对应扩展函数所在的文件名-TextUtil.kt
    public static void text(.....){.....}
}

可以看到Kotlin编译器产生的类的名字跟包含函数的文件的名字一样。文件中所有的顶层函数(声明在一个文件中的函数)都会被编译成这个类的静态方法。因此,从Java中调用这个方法跟调用其他静态方法一样:假设我们的TextUtils文件中还有一个函数

fun test() {
//dosomething
}

当我们在Java中调用时我们只需要:

TextUtilsKt.test();
不可覆盖的扩展函数

方法覆盖在Kotlin对平常的成员函数是有效的,但是你不能覆盖一个扩展函数。比如说你有两个类, View 和它的子类 Button ,同时 Button 类覆盖了来自父类的 click 函数:

open class View {
open fun click() = println("View clicked")
}
class Button: View() { // 1 Button类继承自View类
override fun click() = println("Button clicked")
}

如果你声明了一个View类型的变量,你也可以在这个变量中存储 Button 类型的值,因为 Button 是View的子类。如果你用这个变量调用了一个常规方法,比如 click() ,而且这个方法在Button类中被覆盖了。这个来自 Button 类的覆盖的实现将会被使用:

val view: View = Button()
view.click() // 1 Button类的示例的方法被调用。

但是对于扩展函数而言:扩展函数并不是类的一部分。它们是声明在类的外部的。尽管你可以为某个基类和它的子类用同样的名字和参数类型来定义扩展函数,被调用的函数依赖于已被声明的静态类型的变量,而不是依赖于变量值的运行时类型,例如:

fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
val view: View = Button()
view.showOff() // 1 扩展函数被静态的方式进行解析
I'm a view!

当你用一个 View 类型的变量调用 showOff 时,对应的扩展将会被调用,尽管这个值的真实类型是 Button。因此:覆盖对于扩展函数来说是不起作用的:Kotlin以静态解析它们。

注:如果类有一个成员函数跟一个扩展函数有着相同的签名,成员函数总是优先的。当你扩展类的API时,你应该记住这一点:如果你添加了一个跟你已定义类的客户端(调用者)的扩展函数具有同样的签名成员函数,同时客户端随后重新编译了他们的代码,它将会改变它的含义并开始指向一个新的成员函数

示例:
加入我们Collection添加一个扩展函数,按照一定的格式去打印它里面的元素:

fun <T> Collection<T>.joinToString(collection: Collection<T>,separator: String,prefix: String,postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) // 1 在第一个元素之前不添加分隔符
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

这个函数是支持泛型的:它对包含任意类型的集合有效
使用:

val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
(1; 2; 3)

上面这个函数是实现了我们所需要的功能,但是作为一个有追求的程序员,我们怎能容忍它的可读性如此之差呢(参数列表过长,不知道参数是干嘛的)?这时候我们就需要用到以前说过的指定函数参数的名称并给他我们需要的默认值,因此他可以变成:

fun <T> joinToString(collection: Collection<T>,
                     separator: String = ", ", // 1 默认的参数值
                     prefix: String = "",
                     postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) // 1 在第一个元素之前不添加分隔符
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

使用:

joinToString(list, ", ", "", "")
1, 2, 3
joinToString(list)
1, 2, 3
joinToString(list, "; ")
1; 2; 3

当使用常规调用语法是,我们可以只忽略后面的参数。如果使用命名参数,我们可以忽略列表中的一些(其他)参数而仅仅指定你需要的那些(参数)。

以上就是对Kotlin中扩展函数的总结,理解不到之处,请指正!