Kotlin 扩展函数与属性

扩展函数

kotlin 提供了一种无需继承而实现拓展类或接口的能力。有此能力,您就可以为一些您无法修改的第三方库中的类或接口编写一些新的函数。这些函数可以用通常的方式调用,就好像它们是原始类的方法一样。这种机制称为扩展函数(extension function)。当然你也可以为现有的类定义新的属性,这些属性称之为扩展属性(extension properties)。

使用

要声明扩展函数,需要在新定义的函数前加上接收者类型,它指的是被扩展的类型。下面向 MutableList添加一个交换函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

这里的 this 关键字代表接收对象,也就是调用这个方法的对象:

val list = mutableListOf(123)
list.swap(02// 'this' inside 'swap()' will hold the value of 'list'

此函数对任何 MutableList类型的对象都可以使用,您可以配合泛型使其通用:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

您需要在函数名称之前声明泛型类型参数,以使其在接收者类型表达式中可用。有关泛型的更多信息,请参阅泛型函数。

特性

静态解析

扩展函数和属性实际上并不修改它们所扩展的类。通过定义扩展,您不会将新成员插入到类中,而只是使新函数可以使用此类型变量上的点符号调用。

扩展函数是静态调度的,这意味着一些继承关系的类,并不会体现出子类实现的特性,而是由调用该函数的类型来决定的,而不是在代码运行时计算出来的结果类型决定。例如:

open class Shape
class RectangleShape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
    println(s.getName())
}

printClassName(Rectangle())

上面的例子中,打印结果是 “Shape” ,而不是 “Rectangle” 。因为调用扩展函数的类型是参数 s 的声明类型 Shape 。

类中的定义始终优先于扩展函数,例如如果一个类有一个成员函数,并且定义了一个具有相同接收者类型、相同名称并且适用于给定参数的扩展函数,则该成员总是优先执行。例如:

class Example {
    fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType() { println("Extension function") }

Example().printFunctionType()

执行结果是:Class method 。

但是,扩展函数完全可以重载具有相同名称但参数不同的成员函数:

class Example {
    fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType(i: Int) { println("Extension function #$i") }

Example().printFunctionType(1)

打印结果:Extension function #1

可空接收者

以使用可为空的接收器类型定义扩展。这些扩展可以在对象变量上调用,即使它的值为 null,并且它们可以在扩展函数主体内检查 this == null。

fun Any?.toString(): String {
    if (this == nullreturn "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

扩展属性

val <T> List<T>.lastIndex: Int
    get() = size - 1

和扩展函数使用方法相似,需要接收类型。

由于扩展实际上并没有将成员插入到类中,因此没有有效的方法让扩展属性具有支持字段。这就是扩展属性不允许初始化程序的原因。它们的行为只能通过显式提供 getter/setter 来定义。

例如:

val House.number = 1 // error: initializers are not allowed for extension properties

Companion 对象扩展

如果一个类定义了伴生对象,您还可以为伴生对象定义扩展函数和属性。就像伴生对象的常规成员一样,它们可以仅使用类名作为限定符来调用:

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

扩展范围

在大多数情况下,您在顶层直接在包下定义扩展:

package org.example.declarations

fun List<String>.getLongestString() { /*...*/}

要在其声明包之外使用扩展,请在调用站点导入它:

package org.example.usage

import org.example.declarations.getLongestString

fun main() {
    val list = listOf("red""green""blue")
    list.getLongestString()
}

将扩展声明为成员

您可以在另一个类中为一个类声明扩展。在这样的扩展中,有多个隐式接收器 – 其成员无需限定符即可访问的对象。声明扩展的类的实例称为分派接收器,扩展方法的接收器类型的实例称为扩展接收器。例如:

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }

    fun Host.printConnectionString() {
        printHostname()   // calls Host.printHostname()
        print(":")
        printPort()   // calls Connection.printPort()
    }

    fun connect() {
        /*...*/
        host.printConnectionString()   // calls the extension function
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
    //Host("kotl.in").printConnectionString()  // error, the extension function is unavailable outside Connection
}

打印结果:kotl.in:443

如果调度接收器和扩展接收器的成员之间存在名称冲突,则扩展接收器优先。要引用调度接收者的成员,您可以使用限定的 this 语法。

class Connection {
    fun Host.getConnectionString() {
        toString()         // calls Host.toString()
        this@Connection.toString()  // calls Connection.toString()
    }
}

声明为成员的扩展可以在子类中声明为开放和覆盖。这意味着此类函数的调度对于调度接收器类型是虚拟的,但对于扩展接收器类型是静态的。

open class Base { }

class Derived : Base() { }

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()   // call the extension function
    }
}

class DerivedCallerBaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}

fun main() {
    BaseCaller().call(Base())   // "Base extension function in BaseCaller"
    DerivedCaller().call(Base())  // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
    DerivedCaller().call(Derived())  // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}

可见性注意事项

扩展使用与在相同范围内声明的常规函数相同的可见性修饰符。例如:

  • 在文件顶层声明的扩展可以访问同一文件中的其他 private 顶层声明。
  • 如果扩展在其接收者类型之外声明,则它无法访问接收者的 private 或 protected 成员。

反编译后的源码分析

通过下面的例子来看看编译器是如何实现扩展函数的功能的:

// in package 
fun String.hello(world: String): String {
    return "hello $world"
}

反编译成 Java 后的代码:

public final class ExtensionKt 
    @NotNull 
    public static final String hello(@NotNull String $this$hello, @NotNull String world) 
        Intrinsics.checkParameterIsNotNull($this$hello, "$this$hello"); 
        Intrinsics.checkParameterIsNotNull(world, "world"); 
        return "hello " + world; 
    } 
}

原理:编译器没有修改原来的类,而是在当前文件下生成了一个 “文件名+Kt” 的类,在这个类中生成了一个静态方法,这个静态方法多了一个接收类型的参数。在调用拓展函数时,编译器自动调用这个生成的静态函数,并传入调用对象自身。


原文始发于微信公众号(八千里路山与海):Kotlin 扩展函数与属性

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/84990.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!