Kotlin M5.3:委任されたプロパティ

少し前に、 Kotlinプログラミング言語のもう1つのマイルストーンであるM5.3をリリースしました。

このリリースには、リファクタリングから言語の新機能まで、かなりの数の異なる変更が含まれています。 ここで、最も興味深い変更点についてお話します。委任されたプロパティのサポートです。



多くの人々は言語がサポートすることを望んでいます



原則として、あなたはこれらすべての人々にあなたがやりたいと言うことができますが、人生は難しいと言います...別のオプション:言語の各タイプのプロパティを特別な方法でサポートします...個人的に、私は両方のオプションが好きではありません:くしゃみごとに-言語を開発するときは非常に高価です。 そこで、3番目の方法を選択しました。さまざまなタイプのプロパティを通常のライブラリクラスとして表現できる汎用メカニズムを開発しました。それぞれの言語で個別にサポートする必要はありません。



委任されたプロパティ



例から始めましょう:



class Example { var p: String by Delegate() }
      
      





新しい構文が登場しました。プロパティタイプの後に「by <expression>」と記述できるようになりました。 「by」の後の式はデリゲートです。このプロパティのgetterおよびsetter呼び出しは、この式の値に委任されます。 デリゲートにインターフェイスを実装する必要はありません。特定のシグネチャを持つget()およびset()関数があれば十分です。



 class Delegate() { fun get(thisRef: Any?, prop: PropertyMetadata): String { return "$thisRef, thank you for delegating '${prop.name}' to me!" } fun set(thisRef: Any?, prop: PropertyMetadata, value: String) { println("$value has been assigned") } }
      
      





(インターフェイスを実装するための要件が​​不足していることを恐れる人もいます。落ち着いていれば、恐れないでください。



pプロパティの値を読み取ると、get()関数がDelegateクラスから呼び出され、最初のパラメーターはプロパティが要求されたオブジェクトを渡し、2番目はpプロパティ自体の説明オブジェクト(特に、プロパティの名前を見つけることができます) :



 val e = Example() println(ep)
      
      





この例では、「例@ 33a17727、「p」を委任してくれてありがとう!」が出力されます。



同様に、プロパティレコードが入力されると、set()が呼び出されます。 最初の2つのパラメーターはget()と同じで、3番目は割り当てられたプロパティ値です。



 ep = "NEW"
      
      





この例では、「例@ 33a17727の「p」にNEWが割り当てられました」と出力されます。



おそらく、怠zyなプロパティなどを実装する方法をすでに推測しているでしょうか? 自分で試してみることができますが、そのほとんどはすでにKotlin標準ライブラリに実装されます 。 最も一般的なデリゲートは、 kotlin.properties.Delegatesオブジェクトで定義されてます。



遅延プロパティ



怠zyから始めましょう:



 import kotlin.properties.Delegates class LazySample { val lazy: String by Delegates.lazy { println("computed!") "Hello" } }
      
      





Delegates.lazy()関数は、プロパティ値の遅延計算を実装するデリゲートオブジェクトを返します。get()の最初の呼び出しは、引数としてlazy()に渡されるラムダ式を開始し、受信した値を記憶します。 後続の呼び出しは、単に記憶されたものを返します。



マルチスレッドプログラムで遅延プロパティを使用する場合は、blockingLazy()関数を使用します。これにより、値が正確に1つのスレッドで計算され、正しく公開されます。



観察可能なプロパティ



 class User { var name: String by Delegates.observable("<no name>") { d, old, new -> println("$old -> $new") } }
      
      





observable()関数は、プロパティの初期値と、各割り当てで呼び出されるハンドラー(ラムダ式)の2つの引数を取ります。 ハンドラーには3つのパラメーターがあります。変更するプロパティーの説明、古い値、新しい値です。 特定の値の割り当てを防ぐ必要がある場合は、observable()の代わりにvetoable()関数を使用します。



初期化子のないプロパティ



デリゲートの比較的予期しない使用:多くのユーザーが尋ねます:「初期化する値がない場合、null以外のプロパティを宣言する方法(後で割り当てます)?」 Kotlinでは、初期化子なしで非抽象プロパティを宣言することはできません。



 class Foo { var bar: Bar // error: must be initialized }
      
      





nullを割り当てると、型は「Bar」ではなく「Bar?」になります。また、呼び出しごとにnull参照のケースを処理する必要があります...これで、デリゲートを取得できます。



 class Foo { var bar: Bar by Delegates.notNull() }
      
      





このプロパティが最初の割り当ての前にカウントされる場合、デリゲートは例外をスローします。 初期化後、以前に記録された値を返すだけです。



ハッシュテーブルにプロパティを保存する



ライブラリの最後の例:プロパティをマップに保存します。 これは、たとえばJSONを使用する場合の「動的」コードで役立ちます。



 class User(val map: Map<String, Any?>) { val name: String by Delegates.mapVal(map) val age: Int by Delegates.mapVal(map) }
      
      





このクラスのコンストラクターはマップを受け入れます。



 val user = User(mapOf( "name" to "John Doe", "age" to 25 ))
      
      





デリゲートはストックキーによって値を計算します-プロパティ名:



 println(user.name) // Prints "John Doe" println(user.age) // Prints 25
      
      





変更されたプロパティ(var)は、mapVar()関数を使用してサポートされます。値は同じキーを使用して書き込まれます(これには、MapだけでなくMutableMapが必要です)。



おわりに



委任されたプロパティについて話しました。これは、言語に新しい自由度を追加し、プロパティの操作に独自のセマンティクスを設定できるようにするメカニズムです。 私は表面にある例を示しましたが、このメカニズムを適用する方法は他にもたくさんあるので、歓迎します:考えて実装してください!



PS Kotlin M5.3の他の新製品については、 こちら(英語)をご覧ください



All Articles