原创

Kotlin中的扩展

扩展是Kotlin提供的一种非常优秀的机制,Kotlin通过扩展可以弥补Java作为静态语言灵活性不足的问题。

Kotlin的扩展是一个很独特的功能。Kotlin支持扩展方法和扩展属性。

一、扩展方法

扩展方法其实就是定义一个函数,只是函数名不要写成简单的函数,而是要在函数名前增加被扩展的类(或接口)名和点号(.)。

open class Raw {
    fun test() {
        println("test方法")
    }
}

//定义一个函数,函数名为“被扩展类.方法”
fun Raw.info() {
    println("*******扩展的info方法********")
}

fun main(args: Array<String>) {
    var t = Raw()
    t.test()
    t.info()
}

输出结果:

test方法
*******扩展的info方法********

也可以为Kotlin系统提供的类增加方法。

下例是一个扩展方法用于对集合元素进行随机排列。

fun <T> List<T>.shuffle(): List<T> {
    val size = this.size
    //indexArr用于保存List集合的索引的随机排列
    var indexArr = Array<Int>(size, { 0 })
    var result: MutableList<T> = mutableListOf()
    //创建随机对象
    val rand = Random()
    var i = 0
    outer@ while (i < size) {
        //生成随机数
        var r = rand.nextInt(size)
        for (j in 0 until i) {
            //如果r和前面以生成的任意数字相等,则该随机数不可用,需要重新生成
            if (r == indexArr[j]) {
                continue@outer
            }
        }
        //将随机数r存入indexArr数组中
        indexArr[i] = r
        //根据随机的索引读取List集合元素,并将元素添加到result集合中
        result.add(this[r])
        i++
    }
    return result
}

fun main(args: Array<String>) {
    var nums= listOf(20,30,100,34,67)
    println(nums.shuffle())
    println(nums.shuffle())
}

输出结果:

[30, 20, 34, 67, 100]
[100, 20, 34, 67, 30]

二、扩展的实现机制

Kotlin的扩展并没有真正地修改所扩展的类,其本质就是定义了一个函数,当程序用对象调用扩展方法时,Kotlin在编译时会执行静态解析——根据调用对象、方法名找到扩展函数,转换为函数调用。

扩展方法是由其所在的表达式的编译时类型决定的,而不是由它所在表达式的运行时类型决定的。

open class Base

class Sub : Base()

fun Base.foo() = println("Base扩展的foo()方法")

fun Sub.foo() = println("Sub扩展的foo()方法")

fun invokeFoo(b: Base) {
    b.foo()
}

fun main(args: Array<String>) {
    invokeFoo(Sub())
}

输出结果:

Base扩展的foo()方法

成员方法执行动态解析(由运行时类型决定) 扩展方法执行静态解析(由编译时类型决定)

如果一个类包含了具有相同签名的成员方法和扩展方法,当程序调用这个方法时,系统总是会执行成员方法,而不会执行扩展方法。

class ExtensionAndMember {
    //为该类定义成员方法:foo()
    fun foo() = println("成员方法")
}

//为ExtensionAndMember类定义扩展方法:foo()
fun ExtensionAndMember.foo() = println("扩展方法")

fun main(args: Array<String>) {
    var ea = ExtensionAndMember()
    ea.foo()
}

输出结果:

成员方法

在Java代码中调用Kotlin扩展方法与Kotlin本身调用扩展方法的区别在于:

  • Kotlin编译器本身支持静态解析,因此程序可直接用对象调用扩展方法;
  • Java编译器本身不支持静态解析,因此需要开发者写代码时就做好静态解析。

三、为可空类型扩展方法

Kotlin还允许你为可空类型扩展方法。由于可空类型允许接受null值,使得null值也可调用该扩展方法。

如下例:

fun Any?.equals(other: Any?): Boolean {
    if (this == null) {
        return if (other == null) true else false
    }
    return other.equals(other)
}

fun main(args: Array<String>) {
    var a = null
    println(a.equals(null))
    println(a.equals("Kotlin"))
}

输出结果:

true
false

四、扩展属性

Kotlin也允许扩展属性,Kotlin扩展的属性其实是通过添加getter、setter方法实现的,没有幕后字段。

扩展属性只能是计算属性。

对扩展属性有如下两个限制:

  • 扩展属性不能有初始值(没有存储属性值的幕后字段)
  • 不能用field关键字显式访问幕后字段
  • 扩展只读属性必须提供getter方法;扩展读写属性必须提供getter、setter方法。
class User(var first: String, var last: String) {}

//为User扩展读写属性
var User.fullName: String
    get() = "${first}.${last}"
    set(value) {
        println("执行扩展属性fullName的setter方法")
        //value字符串中不包含.或包含几个.都不行
        if ("." !in value || value.indexOf(".") != value.lastIndexOf(".")) {
            println("您输入的fullName不合法")
        } else {
            var tokens = value.split(".")
            first = tokens[0]
            last = tokens[1]
        }
    }

fun main(args: Array<String>) {
    var user = User("悟空", "孙")
    println(user.fullName)
    user.fullName = "八戒.猪"
    println(user.first)
    println(user.last)
}

输出结果:

悟空.孙
执行扩展属性fullName的setter方法
八戒
猪

五、以成员方式定义扩展

对于以类成员方式定义的扩展,一方面它属于被扩展的类,在扩展方法中可直接调用被扩展类的成员;另一方面它位于定义它所在类的类体中,因此在扩展方法中又可直接调用它所在类的成员。

class A {
    fun bar() = println("A的bar方法")
}

class B {
    fun baz() = println("B的baz方法")
    //以成员方式为A扩展foo()方法
    fun A.foo() {
        bar()
        baz()
    }

    fun test(target: A) {
        target.bar()
        target.foo()
    }
}

fun main(args: Array<String>) {
    var b = B()
    b.test(A())
}

输出结果:

A的bar方法
A的bar方法
B的baz方法

如果被扩展类和扩展定义所在的类包含了同名的方法,就会导致:程序在扩展方法中调用两个类都包含的方法时,系统总是优先调用被扩展类的方法

class Tiger {
    fun foo() {
        println("Tiger类的foo()方法")
    }
}

class Bear {
    fun foo() {
        println("Bear类的foo()方法")
    }

    //以成员方式为Tiger类扩展test()方法
    fun Tiger.test() {
        foo()
        //使用带标签的this指定调用Bear的foo()方法
        this@Bear.foo()
    }

    fun info(tiger: Tiger) {
        tiger.test()
    }
}

fun main(args: Array<String>) {
    val b = Bear()
    b.info(Tiger())
}

输出结果:

Tiger类的foo()方法
Bear类的foo()方法

Kotlin的this支持用“@类名”形式。

六、带接收者的匿名函数

Kotlin支持为类扩展匿名函数,该匿名函数所属的类也是该函数的接收者。所以也被称为“带接收者的匿名函数”。

//定义一个带接收者的匿名函数
val factorial = fun Int.(): Int {
    if (this < 0) {
        return -1
    } else if (this == 1) {
        return 1
    } else {
        var result = 1
        for (i in 1..this) {
            result *= i
        }
        return result
    }
}

fun main(args: Array<String>) {
    println(6.factorial())
}

输出结果:

720

如果接收者类型可通过上下文推断出来,那么Kotlin允许使用Lambda表达式作为带接收者的匿名函数。


class HTML {
    fun body() {
        println(" <body></body>")
    }

    fun head() {
        println("  <head></head>")
    }
}

fun html(init: HTML.() -> Unit) {
    println("<html>")
    val html = HTML()
    html.init()
    println("</html>")
}

fun main(args: Array<String>) {
    html { 
        head()
        body()
    }
}

输出结果:

<html>
  <head></head>
 <body></body>
</html>

如果调用函数只有一个Lambda表达式参数,可以省略调用函数的圆括号,将Lambda表达式放在函数之外即可。

七、何时使用扩展

扩展的作用主要有如下两个方面:

  • 扩展可动态地为已有的类添加方法或属性。
  • 扩展能以更好的形式组织一些工具方法。

扩展是一种非常灵活的动态机制,既不需要使用继承,也不需要使用类似于装饰者的任何设计模式,就可以为现有的类增加功能。

学海无涯苦作舟

我的微信公众号.jpg

基本语法
  • 作者:HunterArley (联系作者)
  • 发表时间:2019-11-26 10:19
  • 版权声明:本网站部分内容转载于合作站点或其他站点,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
  • 公众号转载:请在文末添加作者公众号二维码