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("Runoob")
user.Print()
}
インスタンス実行出力結果
用户名 Runoob
次のコードは、MutableListにスワップ関数を追加します
// 扩展函数 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キーワードは、レシーバーオブジェクト(つまり、拡張関数を呼び出すときにドットの前に指定されたオブジェクトインスタンス)を参照します。
拡張機能は静的に解決されます
拡張関数は、レシーバータイプの仮想メンバーではなく、静的に解決されます。拡張関数が呼び出されると、呼び出される特定の関数は、の動的タイプではなく、呼び出し元の関数のオブジェクト式によって決定されます。
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()
}
インスタンス実行の出力は次のとおりです。
成员函数
空のオブジェクトを拡張します
拡張関数では、これを使用してレシーバーが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ファイルで定義できますが、関数では定義できません。初期化プロパティはバッキングフィールドがないため初期化できず、明示的に提供されたゲッター/セッターによってのみ定義できます。
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() { …… }
定義されたパッケージ外の拡張機能を使用するには、インポートを使用して拡張機能の関数名をインポートして使用します
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は拡張の受信者です。上記の例から、拡張関数では、ディスパッチ受信者のメンバー関数を呼び出すことができることがはっきりとわかります。
関数を呼び出していて、その関数がディストリビューションレシーバーと拡張レシーバーの両方に存在する場合、拡張レシーバーが優先されます。ディストリビューションレシーバーのメンバーを参照するには、制限付きのこの構文を使用できます。
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 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
コンパニオンオブジェクトのメンバーは、Javaの静的メンバーと同等です。そのライフサイクルには常にクラスが伴います。変数と関数はコンパニオンオブジェクトで定義できます。これらの変数と関数は、クラス名で直接参照できます。
コンパニオンオブジェクト拡張関数には、クラス内で拡張する形式とクラス外で拡張する形式の2つの形式があります。これら2つの形式の拡張関数は、相互に影響を与えません(名前が同じ)、名前が同じであっても、それらは完全に2つの異なる機能であり、次の特徴があります。
- (1)クラス内の拡張のコンパニオンオブジェクト関数とクラス外の拡張のコンパニオンオブジェクトは同じ名前を持つことができます。これらは2つの独立した関数であり、相互に影響を与えません。
- (2)クラス内で展開されたコンパニオンオブジェクト関数とクラス外で展開されたコンパニオンオブジェクトが同じ名前の場合、クラス内の他の関数は、クラス内で展開されたコンパニオンオブジェクト関数を優先的に参照します。クラス、クラスの拡張シールドクラス外拡張
- (3)クラス内で拡張されたコンパニオンオブジェクト関数は、クラス内の関数によってのみ参照でき、クラス外の関数およびコンパニオンオブジェクト内の関数からは参照できません。
- (4)クラス外に拡張されたコンパニオンオブジェクト関数は、コンパニオンオブジェクト内の関数から参照できます。
class MyClass {
companion object {
val myClassField1: Int = 1
var myClassField2 = "this is myClassField2"
fun companionFun1() {
println("this is 1st companion function.")
foo()
}
fun companionFun2() {
println("this is 2st companion function.")
companionFun1()
}
}
fun MyClass.Companion.foo() {
println("伴随对象的扩展函数(内部)")
}
fun test2() {
MyClass.foo()
}
init {
test2()
}
}
val MyClass.Companion.no: Int
get() = 10
fun MyClass.Companion.foo() {
println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {
println("no:${MyClass.no}")
println("field1:${MyClass.myClassField1}")
println("field2:${MyClass.myClassField2}")
MyClass.foo()
MyClass.companionFun2()
}
運転結果
no:10
field1:1
field2:this is myClassField2
foo 伴随对象外部扩展函数
this is 2st companion function.
this is 1st companion function.
foo 伴随对象外部扩展函数