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}")
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}")
複数の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
下のように委譲先(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}")
myClass1.property = "2"
Log.d("Test", "${MyClass2(myClass1).property}")
委譲プロパティについて
上記ではクラスに対してbyを使いましたがプロパティーに使う事もできます。
具体的な使い方は下の通りです。
myPropertyのgetterとsettterをDelegateClassに委譲しています。
Log.d("Test", "${MyClass().myProperty}")
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")
Log.d("Test", "$value")
Log.d("Test", "${property.name}")
}
}
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}")
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}")
MyClass(map).myProperty = "2"
Log.d("Test", "${MyClass(map).myProperty}")