委譲プロパティ
| 対応: | Kotlin 1.0(2016) |
|---|
プロパティの読み書き処理を別のオブジェクトに委譲する仕組みです。委譲(delegation)とは、プロパティの読み書き処理を別のオブジェクトに任せる仕組みです。『by』キーワードで標準の委譲プロパティや自作デリゲートを指定できます。
構文
// 委譲プロパティの基本構文
var プロパティ名: 型 by デリゲートオブジェクト
// 標準ライブラリの委譲プロパティ
import kotlin.properties.Delegates
var プロパティ名: 型 by Delegates.observable(初期値) { prop, old, new -> }
var プロパティ名: 型 by Delegates.notNull()
構文一覧
| デリゲート | 概要 |
|---|---|
| by lazy { } | 初回アクセス時に初期化する遅延プロパティです。 |
| Delegates.observable(初期値) { prop, old, new -> } | 値が変わるたびにコールバックを呼び出します。変更を監視したいプロパティに使います。 |
| Delegates.vetoable(初期値) { prop, old, new -> } | コールバックが『true』を返した場合のみ値を更新します。バリデーションに使います。 |
| Delegates.notNull() | 初期値なしで宣言できますが、初期化前にアクセスすると例外が発生します。 |
サンプルコード
sample_delegated_property.kt
import kotlin.properties.Delegates
class User {
// 値の変更をログに出力する
var name: String by Delegates.observable("(未設定)") { _, old, new ->
println("名前が変更されました: $old → $new")
}
// 0以上の場合のみ値を更新する
var age: Int by Delegates.vetoable(0) { _, _, new ->
new >= 0
}
// 初期化を必須にする
var id: Int by Delegates.notNull()
}
fun main() {
val user = User()
user.id = 1001 // notNull の初期化
user.name = "岡部倫太郎" // コールバックが呼ばれる
user.name = "牧瀬紅莉栖" // 再度コールバックが呼ばれる
user.age = 18
println(user.age) // 18
user.age = -1 // 0未満は拒否される
println(user.age) // 18(変わっていない)
}
コンパイルして実行すると次のようになります。
kotlinc sample_delegated_property.kt -include-runtime -d sample_delegated_property.jar java -jar sample_delegated_property.jar 名前が変更されました: (未設定) → 岡部倫太郎 名前が変更されました: 岡部倫太郎 → 牧瀬紅莉栖 18 18
よくあるミス
『notNull()』は初期化前のアクセスで例外になります。『by lazy』は『val』専用で『var』には使えません。
import kotlin.properties.Delegates
class LabMember {
// NG: notNull は初期化前にアクセスすると IllegalStateException
var name: String by Delegates.notNull()
// println(name) ← 初期化前にアクセスするとクラッシュ
// NG: by lazy は var には使えない(コンパイルエラー)
// var lazyProp: String by lazy { "初期化" }
// NG: vetoable のラムダで Boolean 式を返し忘れると Unit になりコンパイルエラー
// var divergence: Double by Delegates.vetoable(0.0) { _, _, new ->
// println(new) // Unit を返すとコンパイルエラー
// }
}
sample_delegated_property_mistakes.kt
import kotlin.properties.Delegates
class LabMember {
var name: String by Delegates.notNull() // 必ず代入してからアクセスする
val lazyProp: String by lazy { // val で宣言する
println("初期化実行")
"ラボメン"
}
var divergence: Double by Delegates.vetoable(0.0) { _, _, new ->
new in 0.0..2.0 // Boolean 式を最後に返す
}
}
fun main() {
val okabe = LabMember()
okabe.name = "岡部倫太郎"
println(okabe.name) // 岡部倫太郎
println(okabe.lazyProp) // 初期化実行 → ラボメン
println(okabe.lazyProp) // ラボメン(2回目は初期化されない)
okabe.divergence = 1.048596
println(okabe.divergence) // 1.048596
okabe.divergence = 3.0 // 範囲外なので拒否される
println(okabe.divergence) // 1.048596(変わっていない)
}
コンパイルして実行すると次のようになります。
kotlinc sample_delegated_property_mistakes.kt -include-runtime -d sample_delegated_property_mistakes.jar java -jar sample_delegated_property_mistakes.jar 岡部倫太郎 初期化実行 ラボメン ラボメン 1.048596 1.048596
概要
委譲プロパティは、プロパティのアクセスロジックを再利用可能なクラスに切り出す仕組みです。『by』の後ろにデリゲートオブジェクトを指定するだけで、複雑なアクセス処理をシンプルに記述できます。
『Delegates.observable』は値の変更を監視したいとき、『Delegates.vetoable』はバリデーションを挟みたいときに便利です。自作のデリゲートを作る場合は『ReadWriteProperty<T, V>』インターフェースを実装して『getValue』と『setValue』を定義してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。