Kotlin from Learning to Android Chapter 10 Extension

Similar to C# and Gosu, Kotlin can also extend a new function for a class without inheriting from the class or using any kind of design pattern, such as decoration (Decorator). This is done through a special kind of declaration called an extension . Kotlin supports extension functions and extension properties.

extension function

When declaring an extension function, we need to add a receiving type before the function name, which is the type being extended. For example: The following code adds a new swap function to MutableList.

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] 
    this[index1] = this[index2]
    this[index2] = tmp
}

This in the above code represents MutableList. After extending the swap function, the data of MutableList type can directly use the swap function:

val l = mutableListOf(1, 2, 3)
l.swap(0, 2)

Of course, we can also use generics in extension functions:

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
}

For children's shoes who are not familiar with Kotlin, the above code may not be understandable. Let's extend a function for our own class to demonstrate the extended functions of Kotlin:

fun main(args: Array<String>) {
    var ec = ExtensionedClass()
    ec.nameSetter("admin")
    ec.alertName()          // 这里可以直接调用扩展函数
    println(ec.name)        // hello admin , 可见扩展函数调用成功
}

// 为 ExtensionedClass 添加一个新的函数
fun ExtensionedClass.alertName(){
    this.name = "hello " + this.name
}

class ExtensionedClass{

    var name: String? = null

    fun nameSetter(str: String){
        name = str
    }
}

Extended parsing static

Extensions don't actually modify the classes they extend. By defining an extension, you don't need to insert new members into a class, but just use the variable of this type.Extension function to call.

We want to emphasize that extension functions are statically dispatched, that is, they are not implemented by the receiver type. This means that the extension function that is called is determined by the type of the expression that calls the function, not by the result of evaluating the expression at runtime. For example:

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())           // c

The result of printing is c, because the called extension function only depends on the declared parameter C type, that is, the C class.

If a class has a member function, and an extension function is defined with the same receiver type, same name, and applies to the given arguments, then the member function will always be called when invoked. For example:

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

If we call c.foo(), it will print "member", not "extension".

However, it is perfectly fine for extension functions to overload member functions that have the same name but different signatures:

class C {
    fun foo() { println("member") }
}

fun C.foo(i: Int) { println("extension") }

Calling C().foo(1) will print "extension".

nullable receiver

Note that extensions can be defined with a nullable receiver type. Such an extension can be called on an object variable even if its value is null, and can check this == null in the function body, which will allow you to call the toString() function in Kotlin without checking for null: because in detected in the extension function.

fun Any?.toString(): String {
    if (this == null) return "null"
    // 检测完是否是 null 后,下面的 this 会自动转化为非 null 类型
    return toString()
}

extended attributes

Extension properties are similar to extension functions:

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

Note that there is no valid way for extension properties to have a backing field since the extension does not actually insert the member into the class. That's why initializers don't allow extension properties. Their behavior can only be defined by explicitly providing getters/setters. For example:

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

Companion object extension

If a class has a companion object , then you can also define extension functions and extension properties for the companion object:

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

fun MyClass.Companion.foo() {
    // ...
}

Just like regular members of a companion object, they can be called using only the class name as a qualifier:

MyClass.foo()

Extended scope

Most of the time, we define extensions at the top level, that is, directly under the package:

package foo.bar

fun Baz.goo() { ... } 

To use such an extension outside of its declaration package, we need to import it at the call site

package com.example.usage

import foo.bar.goo // 只引入 goo
                   // 或者
import foo.bar.*   // 全部引入

fun usage(baz: Baz) {
    baz.goo()
}

declare extension members

In a class, you can declare extensions for another class. In such extensions, there are multiple implicit receiver-object members that can be accessed without qualifiers. An instance of a class that extends the declared class is called a dispatch receiver, while an instance of the receiver type of an extension method is called an extension receiver.

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

If a name conflict occurs between a dispatch receiver and an extension receiver, the extension receiver takes precedence. To refer to members of a dispatch receiver, you can use qualified syntax .

class C {
    fun D.foo() {
        toString()         // D.toString()
        [email protected]()  // C.toString()
    }

Extensions declared as members can be declared open and overridden in subclasses. This means that dispatching these functions is virtual for dispatch receiver types, but static for extension receiver types.

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

C().caller(D())   // "D.foo in C"
C1().caller(D())  // "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1())  // "D.foo in C" - extension receiver is resolved statically

Motivation for using extensions

In Java, we usually name some utility classes, such as: FileUtils, StringUtils and so on. The well-known class java.util.Collections also belongs to this style. But when using these tool classes, it will make us feel uncomfortable, for example:

// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))

Of course, in order to simplify the code, we can achieve the following effects through static import:

// Java
swap(list, binarySearch(list, max(otherList)), max(list))

This is slightly better, but the IDE's powerful code completion function is still too little help for us. It would be nice if it could be like this:

// Java
list.swap(list.binarySearch(otherList.max()), list.max())

However, we don't want to implement all possible methods in the class, so this is the advantage of the extended function.

Guess you like

Origin blog.csdn.net/niuzhucedenglu/article/details/73144416