言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Kotlin辞典

  1. トップページ
  2. Kotlin辞典
  3. 委譲プロパティ

委譲プロパティ

対応: 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』を定義してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。