Kotlinのbyというキーワードが分からなかったので調べてみました。
byを使ってみる
byとは委譲に使うキーワードです。
実際に動かしながら委譲やbyについて見ていきます。
まずはInterfaceとInterfaceを適用したクラスを作成します。
interface TestInterface { val property: String fun method(): Int } class MyClass1: TestInterface { override val property = "1" override fun method(): Int { return 1 } }
次に同じInterfaceを適用したクラスを作成します。
このクラスは初期化時にMyClass1を受け取り、内部ではMyClass1のメソッドやプロパティーを呼び出します。
class MyClass2(private val myClass1: MyClass1): TestInterface { override val property = myClass1.property override fun method(): Int { return myClass1.method() } }
実際に動かしてみるとMyClass1のプロパティーが呼ばれている事が分かります。
このように外部のクラスのプロパティーやメソッドを利用する事を委譲といいます。
byというキーワードは委譲を簡単にするキーワードです。
Log.d("Test", "${MyClass2(MyClass1()).property}") // → 1
byを使ってMyClass2を書き直すと下のようになります。
これでTestInterfaceに関する処理はすべてmyClass1のものを呼び出すようになります。
class MyClass2(private val myClass1: MyClass1): TestInterface by myClass1
下のように一部の処理だけは委譲しない事も可能です。
class MyClass2(private val myClass1: MyClass1): TestInterface by myClass1 { override val property = "2" } Log.d("Test", "${MyClass2(MyClass1()).property}") // → 2
複数のInterfaceを適用して委譲する場合はそれぞれにbyを付ける必要があります。
interface TestInterface { val property: String fun method(): Int } interface TestInterface2 { val property2: String } class MyClass1: TestInterface, TestInterface2 { override val property = "1" override fun method(): Int { return 1 } override val property2 = "property2" } class MyClass2(private val myClass1: MyClass1): TestInterface by myClass1, TestInterface2 by myClass1
下のようにインスタンスとInterfaceの型があってない場合はエラーになります。
もしTestInterface2用のプロパティーを実装してあってもエラーです。
class MyClass1: TestInterface { override val property = "1" override fun method(): Int { return 1 } val property2 = "property2" } class MyClass2(private val myClass1: MyClass1): TestInterface2 by myClass1 // → Type mismatch.エラー
下のように委譲先(MyClass1)のプロパティーの値を変えるとMyClass2のproperty呼び出し時の値も変わります。
interface TestInterface { val property: String } class MyClass1: TestInterface { override var property = "1" } class MyClass2(private val myClass1: MyClass1): TestInterface by myClass1 val myClass1 = MyClass1() Log.d("Test", "${MyClass2(myClass1).property}") // → 1 myClass1.property = "2" Log.d("Test", "${MyClass2(myClass1).property}") // → 2
委譲プロパティについて
上記ではクラスに対してbyを使いましたがプロパティーに使う事もできます。
具体的な使い方は下の通りです。
myPropertyのgetterとsettterをDelegateClassに委譲しています。
Log.d("Test", "${MyClass().myProperty}") // → 1 MyClass().myProperty = "2" class DelegateClass { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "1" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { Log.d("Test", "$thisRef") // → com.example.myapp.MyClass@4dea06e Log.d("Test", "$value") // → 2 Log.d("Test", "${property.name}") // → myProperty } } class MyClass { var myProperty: String by DelegateClass() }
同じDelegateインスタンスを使い回す事で、下のように値を複数クラスで共有する事もできます。
class DelegateClass { var myProperty = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return myProperty } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { myProperty = value } } class MyClass(delegate: DelegateClass) { var myProperty: String by delegate } val delegate = DelegateClass() Log.d("Test", "${MyClass(delegate).myProperty}") // → "" MyClass(delegate).myProperty = "2" Log.d("Test", "${MyClass(delegate).myProperty}") // → 2
Mapとbyについて
byを使うとMapの中の値にアクセスする事も可能です。
具体的には下の通りです、JSONデータの展開などで活躍しそうです。
class MyClass(delegate: Map<String, String>) { val myProperty: String by delegate } Log.d("Test", "${MyClass(mapOf("myProperty" to "1")).myProperty}")
Mapに該当のキーがない場合、NoSuchElementExceptionエラーになります。
class MyClass(delegate: Map<String, String>) { val myProperty: String by delegate } Log.d("Test", "${MyClass(mapOf("myProperty2" to "1")).myProperty}")
MutableMapを使う事で値のセットもできるようになります。
class MyClass(delegate: MutableMap<String, String>) { var myProperty: String by delegate } val map = mutableMapOf("myProperty" to "1") Log.d("Test", "${MyClass(map).myProperty}") // → 1 MyClass(map).myProperty = "2" Log.d("Test", "${MyClass(map).myProperty}") // → 2