Kotlin 拡張

Kotlin はクラスのプロパティやメソッドを拡張でき、継承や Decorator パターンを使用する必要はありません。

拡張は静的な動作であり、拡張されるクラスのコード自体には一切影響を与えません。


拡張関数

拡張関数は既存のクラスに新しいメソッドを追加でき、元のクラスを変更しません。拡張関数の定義形式:

fun receiverType.functionName(params){
    body
}
  • receiverType:関数のレシーバー、つまり関数が拡張するオブジェクトを表します
  • functionName:拡張関数の名前
  • params:拡張関数のパラメータで、NULL も可能です

以下の例は User クラスを拡張します:

class User(var name:String)

/**扩展函数**/
fun User.Print(){
    print("用户名 $name")
}

fun main(arg:Array<String>){
    var user = User("サンプル")
    user.Print()
}

インスタンス実行の出力結果は次のとおりです:

用户名 サンプル

以下のコードは MutableList にswap 関数を追加します:

// 扩展函数 swap,调换不同位置的值
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 l = mutableListOf(1, 2, 3)
    // 位置 0 和 2 的值做了互换
    l.swap(0, 2) // 'swap()' 函数内的 'this' 将指向 'l' 的值

    println(l.toString())
}

インスタンス実行の出力結果は次のとおりです:

[3, 2, 1]

this キーワードはレシーバーオブジェクト(receiver object)を指します(つまり、拡張関数を呼び出すときに、ドットの前に指定されたオブジェクトインスタンスです)。


拡張関数は静的に解析される

拡張関数は静的に解析され、レシーバータイプの仮想メンバーではありません。拡張関数を呼び出すとき、実際に呼び出される関数は、呼び出し元のオブジェクト式によって決定され、動的な型によって決定されるわけではありません:

open class C

class D: C()

fun C.foo() = "c"   // 扩展函数 foo

fun D.foo() = "d"   // 扩展函数 foo

fun printFoo(c: C) {
    println(c.foo())  // 类型是 C 类
}

fun main(arg:Array<String>){
    printFoo(D())
}

インスタンス実行の出力結果は次のとおりです:

c

拡張関数とメンバー関数が一致する場合、その関数を使用するときはメンバー関数が優先されます。

class C {
    fun foo() { println("成员函数") }
}

fun C.foo() { println("扩展函数") }

fun main(arg:Array<String>){
    var c = C()
    c.foo()
}

インスタンス実行の出力結果は次のとおりです:

メンバ関数

空オブジェクトの拡張

拡張関数内では、this を使用してレシーバーが NULL かどうかを判断できます。これにより、レシーバーが NULL でも拡張関数を呼び出すことができます。例:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

インスタンス実行の出力結果は次のとおりです:

null

拡張プロパティ

関数に加えて、Kotlin はプロパティの拡張もサポートしています:

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

拡張プロパティはクラスまたは Kotlin ファイル内で定義できますが、関数内では定義できません。初期化プロパティは、プロパティにバッキングフィールド(backing field)がないため、初期化できず、明示的に提供された getter/setter でのみ定義できます。

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

拡張プロパティは val としてのみ宣言できます。


コンパニオンオブジェクトの拡張

クラスにコンパニオンオブジェクトが定義されている場合、コンパニオンオブジェクトに拡張関数とプロパティを定義することもできます。

コンパニオンオブジェクトは「クラス名.」の形式で呼び出されます。コンパニオンオブジェクトで宣言された拡張関数は、クラス名の修飾子を使用して呼び出します:

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴随对象的扩展函数")
}

val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

インスタンス実行の出力結果は次のとおりです:

no:10
伴随对象的扩展函数

拡張のスコープ

通常、拡張関数やプロパティはトップレベルのパッケージの下に定義されます:

package foo.bar

fun Baz.goo() { …… } 

定義されたパッケージの外部で拡張を使用するには、import で拡張された関数名をインポートして使用します:

package com.example.usage

import foo.bar.goo // 导入所有名为 goo 的扩展
                   // 或者
import foo.bar.*   // 从 foo.bar 导入一切

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

拡張をメンバーとして宣言

クラス内部で、別のクラスの拡張を宣言できます。

この拡張には、複数の暗黙的なレシーバーがあります。拡張メソッドが定義されているクラスのインスタンスはディスパッチレシーバーと呼ばれ、拡張メソッドのターゲットタイプのインスタンスは拡張レシーバーと呼ばれます。

class D {
    fun bar() { println("D bar") }
}

class C {
    fun baz() { println("C baz") }

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

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

インスタンス実行の出力結果は次のとおりです:

D bar
C baz

C クラス内で、D クラスの拡張が作成されています。このとき、C はディスパッチレシーバーと呼ばれ、D は拡張レシーバーです。上記の例から、拡張関数内でディスパッチレシーバーのメンバー関数を呼び出せることが明確にわかります。

ある関数を呼び出すとき、その関数がディスパッチレシーバーと拡張レシーバーの両方に存在する場合は、拡張レシーバーが優先されます。ディスパッチレシーバーのメンバーを参照するには、限定された this 構文を使用できます。

class D {
    fun bar() { println("D bar") }
}

class C {
    fun bar() { println("C bar") }  // 与 D 类 的 bar 同名

    fun D.foo() {
        bar()         // 调用 D.bar(),扩展接收者优先
        [email protected]()  // 调用 C.bar()
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

インスタンス実行の出力結果は次のとおりです:

D bar
C bar

メンバーとして定義された拡張関数は、open として宣言でき、サブクラスでオーバーライドできます。つまり、この種の拡張関数のディスパッチプロセスでは、ディスパッチレシーバーに対しては仮想的(virtual)ですが、拡張レシーバーに対しては依然として静的です。

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


fun main(args: Array<String>) {
    C().caller(D())   // 输出 "D.foo in C"
    C1().caller(D())  // 输出 "D.foo in C1" —— 分发接收者虚拟解析
    C().caller(D1())  // 输出 "D.foo in C" —— 扩展接收者静态解析

}

インスタンス実行の出力結果は次のとおりです:

D.foo in C
D.foo in C1
D.foo in C
その他の拡張