kotlin - 扩展方法和扩展属性

kotlin - 扩展方法和扩展属性

我们都知道java要扩展一个已有类的方法和属性必须采用继承、组合或直接修改现有类来进行功能和属性的扩展。而kotlin是完全支持扩展方法和扩展属性的,这样我们就可以像正常调用对象方法一样使用扩展方法和属性了。

扩展方法

扩展方法的定义语法如下:

fun [扩展类].(参数列表){
    //方法执行体
}

示例代码如下:

class Row{
    fun test(){
        println("test")
    }
}
//给Row扩展了一个info方法
fun Row.info(){
    println("info")
}

fun main(args:Array<String>) {
    val row = Row()
    row.test()
    //使用的时候和调用普通方法没有任何区别
    row.info()
}

kotlin也是支持泛型类的扩展的,比如我们为List添加一个交换两个元素位置的方法代码如下:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
}
//使用
fun main(args:Array<String>) {
    val list = mutableListOf(1, 2, 3)
    list.swap(0,1)
    println("list = $list")
}

扩展的实现机制

kotlin的扩展其实是没有修改原来的类的。kotlin扩展的本质其实就是定义了一个函数,当程序用对象调用扩展方法时,kotlin在编译时会执行静态解析(就是根据调用对象、方法名来找到扩展函数,转换为函数调用)。
kotlin在编译时会经过一下几步:

  • 检查调用对象的类型
  • 检查对象所属的类是否定义了该方法,如果类本身定义了该方法,kotlin不需要任何处理直接执行。
  • 如果类本身不包含这个方法,kotlin会检查程序中是不是有这个类的对应扩展方法,如果有,kotlin会执行静态解析,会将调用处的方法替换为这个扩展函数。
  • 如果类不包含对应的方法也在程序中没有找到相应的扩展函数,编译器将会报错。
    在kotlin中成员方法执行动态解析(调用哪个方法由运行时类型决定),扩展方法执行静态解析(调用哪个方法由编译时类型决定)
fun main(args: Array<String>) {
    //传入的对象是Sub
    invokeMethod(Sub())
}

open class Base1
class Sub : Base1()

fun Sub.test() {
    println("Sub扩展的 test方法")
}

fun Base1.test() {
    println("Base1扩展的 test方法")
}

fun invokeMethod(base1: Base1) {
    base1.test()
}

如果test方法是Base1、Sub的成员方法,那么一定由JVM动态调用运行时类型(Sub)对象的方法。但此处test是Base1、Sub的扩展方法,对于扩展方法kotlin编译器执行的是静态解析,所以在编译阶段kotlin编译器只知道invokeMethod()方法的参数是Base1类型,因此Kotlin编译器会将其替换为Base1的test方法。所以上面的程序打印的结果为:

Base1扩展的 test方法

如果扩展方法和成员方法的方法签名一致那么就优先调用成员方法。
示例代码如下:

class ExtensionAndMember {
    //为该类定义成员方法: foo ()
    fun foo() = println("成员方法")
}
//为 ExtensionAndMember 类定义扩展方法: foo ()
fun ExtensionAndMember.foo() = println("扩展方法")
//使用
fun main(args: Array<String>) {
    val ea = ExtensionAndMember()
    ea.foo()
}

java类调用kotlin的扩展方法

java类也是可以调用kotlin的扩展方法的,只不过需要自己解决java不支持静态解析的问题。示例代码如下:

public class ExtendJavaCallTest {
    public static void main(String[] args) {
        ExtensionAndMember extensionAndMember = new ExtensionAndMember();
        ExtendMethodDemoKt.foo(extensionAndMember);
    }
}

可空类型扩展方法

kotlin也支持为可空类型定义扩展方法,只是需要我们做null值调用该扩展方法的情形。示例代码如下:

//为可空类型扩展equals 方法
fun Any?.equals(other: Any?): Boolean {
    if (this == null) {
        return if (other == null) true else false
    }
    return this.equals(other)
}
//使用
fun main(args: Array<String>) {
    var a = null
    println(a.equals(null)) //输出 true
    println(a.equals("Kotlin")) //输出 false
}

扩展属性

扩展属性有以下原则:

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

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

以成员的方式扩展方法

上面的都是以顶级的方法去定义扩展方法(属性)。kotlin也支持以成员的方法定义扩展和属性。
对于成员的方式定义的扩展,一方面属于被扩展的类,因此可以访问被扩展类的方法(属性);另一方面它还可以在扩展方法(属性)中调用它所在类的成员。
示例代码如下:

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

class B {
    fun baz() = println("B类的baz方法")
    fun A.foo() {
        this.bar()//this指的是被扩展类。
        this@B.baz()//this可以省略
    }

    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方法

注意:如果扩展类和扩展方法所在类中的方法签名一致,那么如果不加this@类名指定调用哪个类的方法时,默认调用的是被扩展类的同名方法

扩展匿名函数

kotlin还支持为类扩展匿名函数,该扩展函数所属的类也是该函数的接收者,所以也被称为“带接收者的匿名函数”。
与普通的扩展函数有何不同:
与普通的扩展函数没有什么不同只是书写格式不同,不需要给扩展函数定义名字而已。具体格式如下:

fun 类名.(参数列表)

示例代码如下:

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())
}

带接收者匿名函数也是有类型的,该类型就是在普通函数类型前面加了一个接收者类型。比如上面的factorial的类型如下:

Int.()->Int

带接收者的匿名函数也可以作为方法的参数,如果接收者的类型可以通过上下文推断出来,kotlin也允许lambda表达式作为带接收者的匿名函数。比如,如下代码:

class Car {
    fun star() = println("车启动了")
    fun run() = println("车跑了")
}

fun driveCar(autoDrive: Car.() -> Unit) {
    println("准备自驾游喽~~")
    var car = Car()
    car.autoDrive()//使用接收者调用auttoDrive引用的匿名函数
    println("到站停车了。。。。。")
}
//测试程序
fun main(args: Array<String>) {
    //验证带接收者的匿名函数可以使用lambda表达式
    driveCar {
        star()
        run()
    }
}

什么情况用扩展

  • 想要动态的为已有的类添加方法或属性时。
  • 需要用更好的方式组织一些工具方法时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值