※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリは参考サイトの翻訳をベースに、加筆・変更を施した構成となっています。
参考サイト http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields
Javaにおけるフィールド・アクセサ
Javaの世界で私たちは、フィールドのゲッタやセッタを書くことに慣れています。言うまでもありませんが、Effective Javaの項目14「In public classes, use accessor methods, not public fields*1」によるアドバイスです。メジャーなIDEは、ゲッタやセッタを自動生成して私たちを助けてくれます。しかし、そのようなクラスは決まり文句だらけになってしまうことがあります。
field/get/setの退治
Kotlinにはフィールドを宣言する方法がありません。すべてがプロパティです。読み書きできるプロパティは varキーワードで宣言され、読み取り専用プロパティは valキーワードです。
public class Address() { // parentheses denote a _primary constructor_ public var name : String public var street String public var city : String public var state : String? public var zip : String }http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
ここに5つのミュータブルなプロパティがあり、それぞれ値が格納されているバッキング・フィールドと2つのアクセサ(ゲッタとセッタ)を持っています。
プロパティを使うには、まるでJavaのフィールドであるかのように、名前で参照します。
fun copyAddress(address : Address) : Address { val result = Address() // there's no 'new' keyword in Kotlin result.name = address.name // accessors are called result.street = address.street // ... return result }http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
プロパティとアクセサの宣言
ミュータブルなプロパティの宣言の完全な構文は次のとおりです。
var <propertyName> : <PropertyType> [= <property_initializer>] <getter> <setter>http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
ゲッタとセッタ、イニシャライザは任意です。Property type(プロパティの型)は、イニシャライザやオーバライドしている基底クラスのメンバから推論できる場合は省略することができます。
例えば
var allByDefault : Int? // nullで初期化、デフォルトのゲッタとセッタを持つ var initialized = 1 // Int型, デフォルトのゲッタとセッタを持つ var setterVisibility : String = "abc" // イニシャライザは必須, null許容型ではない private set // セッタは privateで、デフォルトの実装を持つ
イミュータブルなプロパティ宣言の完全な構文は、ミュータブルなものと2点だけ異なります。それは var の代わりに val を使うことと、セッタが許されないことです。
val simple : Int? // Int型、デフォルトのゲッタを持ち、コンストラクタで初期化が必要 val inferredType = 1 // Int型でデフォルトのゲッタを持つ
カスタム・アクセサを書くことができ、それは普通の関数に非常に似ています。ここにカスタム・ゲッタの例を示します。
val isEmpty : Boolean get() = this.size == 0http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
このプロパティは純粋に他社から派生しているので、コンパイラはそのバッキング・フィールドを生成しません。
カスタム・セッタは次のような感じです。
var stringRepresentation : String get() = this.toString() set(value) { setDataFromString(value) // parses the string and assigns values to other properties }http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
バッキング・フィールド
上記で述べたとおり、いくつかのプロパティはバッキング・フィールド(backing field)を持ちます。クライアントの視点において、プロパティはアクセサのペア(またはゲッタだけ)ですが、アクセサは本当のフィールドから物理的にデータを読み書きする場合があります。Kotlinでは明示的にフィールドを宣言できません。
単純なケースです。私たちがカスタム・アクセサの実装を提供しないとき、プロパティがバッキング・フィールドを持たなければならないことは明白です。でなければ、デフォルトのアクセサは次のケースにおいて何をすべきなのでしょうか?
var counter : Int = 0http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
しかしカスタム・アクセサがあるとき、それはバッキング・フィールドに依存する場合も、しない場合もあります。
プロパティx のバッキング・フィールドにアクセスするには、$x と書きます(Kotlinではドルマークは識別子の一部に使用できません)。
var counter = 0 // 初期値はバッキング・フィールドに直接書き込まれる set(value) { if (value >= 0) $counter = value }
$counterはcounterのアクセサ内で読み書きでき、コンストラクタ内で割り当てができます。他の場所では$counterにアクセスできません。
コンパイラはアクセサの本体を見て、それらがバッキング・フィールドを使う場合(またはデフォルトのアクセサ実装が残されている場合)はバッキング・フィールドを生成します。それ以外の場合は、生成しません。
例えば、次のケースではバッキング・フィールドは存在しません。
val isEmpty : Boolean get() = this.size > 0http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
アクセサがバッキング・フィールドを参照しないので、それは不要なのです。
従来の方法でやりたくなったら
もし、この「暗黙的なバッキング・フィールド」に収まらないようなことをしたくなったら、常に「バッキング・プロパティ」に立ち戻ることができます。
private var _table : Map<String, Int>? = null public val table : Map<String, Int> get() { if (_table == null) _table = HashMap() // Type parameters are inferred return _table ?: throw AssertionError("Set to null by another thread") }http://confluence.jetbrains.net/display/Kotlin/Properties+And+Fields より引用
すべての点において、これはJavaと同じです。関数呼び出しのオーバヘッドが生じないように、デフォルトのゲッタとセッタでのprivateプロパティへのアクセスが最適化されています。
プロパティのオーバライディング
*1:邦訳:publicクラスでは、publicのフィールドではなく、アクセッサーメソッドを使う