まくまくKotlinノート
クラスにプロパティのアクセサメソッドを定義する (set, get)
2019-05-10

カスタムアクセサとは

Kotlin では、下記のようにクラスのプロパティを定義するだけで、自動的に setter/getter が生成されます。

data class Book(var title: String)

val b = Book("タイトル1")
b.title = "タイトル2"

多くの場合はこれだけで十分ですが、プロパティの値を読み書きする際に何らかの処理を行いたい場合は、プロパティのカスタムアクセサ (getter/setter) を定義する必要があります。

カスタムアクセサを定義する (set() / get())

プロパティにアクセスしたときの振る舞いは、getset を実装することでカスタマイズできます。 下記の例では、title プロパティに対してカスタム getter、setter を定義しています。

class Book(title: String) {
    var title: String = title
        get() {
            println("get: $field")
            return field
        }
        set(value: String) {
            println("set: $field -> $value")
            field = value
        }
}

print(book.title) のようにプロパティの値を参照すると get() が呼び出され、book.title = "あああ" のようにプロパティの値を書き換えようとすると set(value) が呼び出されます。

プロパティとして現在保持されている値は field キーワードで参照できます。 setter の実装の中では、field に値を代入することで、プロパティが保持する値を変更します。 setter が呼び出されたときに、先に field の値を参照すれば、プロパティの値を変更する前の元の値を取得できます。 例えば、下記のようにすれば、プロパティの値が実際に変更されるときのみ処理を行う ということが実現できます。

class Book(title: String) {
    var title: String  = title
        set(value: String) {
            if (field != value) {
                println("値を $field から $value に変更したよ")
                field = value
            }
        }
}

val b = Book("あああ")
b.title = "あああ"  // 何も起こらない
b.title = "いいい"  //=> 値を あああ から いいい に変更したよ

setter のみを非公開 (private) にする

getter や setter の可視性は、デフォルトではプロパティそのものの可視性と等しくなります。 public なプロパティの getter と setter のうち一方を private にしたい場合、setget の前に private キーワードを付けます。

次の Connector クラスは、retryCount プロパティの setter を非公開にしています。 Connector クラス内からのみプロパティの値を変更することができます。

class Connector {
    var retryCount: Int = 0
        private set

    fun retry() {
        ++retryCount
    }
}

val conn = Connector()
conn.retry()
conn.retry()
conn.retry()
println(conn.retryCount)  //=> 3

この例では読み出し専用の isExpensive プロパティを定義しています。

class Book(val title: String, var price: Int) {
    val isExpensive: Boolean
        get() {
            return price > 1000
        }
}

// 使用例
val b = Book("Kotlin ABC", 1500)
println(b.isExpensive)
上記の `isExpensive` の実装は、1 つの式の評価結果を `return` しているだけなので、expression-body の構文を使って、下記のように簡潔に記述することもできます。
val isExpensive: Boolean
    get() = price > 1000
2019-05-10