プロパティ
プロパティの宣言
Kotlinのクラスのプロパティは、var キーワードを使用して、ミュータブル(可変)として宣言することもでき、 val キーワードを使用してイミュータブル(読み取り専用)にすることもできます。
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
プロパティを使うには、単純に名前で参照するだけで良いです:
fun copyAddress(address: Address): Address {
val result = Address() // 'new' キーワードは Kotlin にありません
result.name = address.name // アクセサが呼ばれる
result.street = address.street
// ...
return result
}
ゲッターとセッター
プロパティを宣言するための完全な構文は次のとおりです:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
イニシャライザ(初期化子)、ゲッター(getter)とセッター(setter)は必須ではありません。 プロパティの型も、イニシャライザから推測出来たり、 getterの返す型から推測出来る場合には必須ではありません:
var initialized = 1 // Int型を持ち、デフォルトのゲッターとセッター
// var allByDefault // エラー:明示的なイニシャライザが必要、デフォルトのゲッターとセッターは暗黙
読み取り専用のプロパティ宣言の完全な構文は、ミュータブルのものと比べて2点異なります。var
の代わりに val
で始まるのと、セッターが許されない事です:
val simple: Int? // Int 型を持ち、デフォルトゲッターを持つ。コンストラクタ内で初期化が必要
val inferredType = 1 // Int 型を持ち、デフォルトゲッターを持つ
プロパティのカスタムアクセサを定義する事も出来ます。 カスタムゲッターを定義すると、プロパティにアクセスする都度毎回呼ばれます(このようにして計算プロパティ(computed property)を実装出来ます)。 ここでは、カスタムゲッターの例を示します:
ゲッターから推測出来る場合はプロパティの型は省略出来ます:
val area get() = this.width * this.height
カスタムセッターを定義すると、そのプロパティに値をセットする都度、初期化の時を除いて毎回呼ばれるようになります。 カスタムセッターは次のようになります:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 文字列をパースして他のプロパティへ値を代入する
}
慣例により、セッターの引数名は value
としますが、別の名前が良いならそちらを選択することもできます。
アクセサの可視性を変更したりアノテーションを付ける必要があるけれど、デフォルトの実装を変更したくない場合は、その本体を定義せずにアクセサを定義することができます:
var setterVisibility: String = "abc"
private set // セッターはプライベートでデフォルトの実装を持つ
var setterWithAnnotation: Any? = null
@Inject set // セッターに Inject でアノテーションを付ける
バッキングフィールド
(訳注: Backing Fields)
Kotlinにおいては、フィールドはプロパティの一部としてメモリに値を保持する時にだけ使われます。
フィールドを直接定義する事は出来ません。
しかし、プロパティがバッキングフィールド(backing field)を必要とする時には、Kotlinは自動的にそれを提供します。
このバッキングフィールドはアクセサの中でfield
識別子を使用して参照することができます:
var counter = 0 // イニシャライザはバッキングフィールドに直に書き込む
set(value) {
if (value >= 0)
field = value
// counter = value // エラー スタックオーバーフロー: 実際の名前である'counter'を使うとセッターの再帰呼び出しを引き起こしてしまう
}
field
識別子は、プロパティのアクセサの中でのみ使用することができます。
バッキングフィールドは、プロパティがアクセサのデフォルトの実装のうち少なくとも1つを使用するか、カスタムアクセサが field
識別子でバッキングフィールドを参照した場合に、そのプロパティ用に生成されます。
たとえば、以下のような場合にはバッキングフィールドは存在しません:
val isEmpty: Boolean
get() = this.size == 0
バッキングプロパティ
「暗黙のバッキングフィールド」にそぐわないことをやりたい場合には、 いつでも バッキングプロパティ (backing property) を持つという代替案が使えます:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 型パラメータは推論される
}
return _table ?: throw AssertionError("他スレッドによってnullをセットされた")
}
JVMにおいて: privateプロパティへデフォルトゲッターとセッターでアクセスすると、関数呼び出しのオーバヘッドが無いように最適化されます。
コンパイル時定数
読み取り専用のプロパティの値がコンパイル時にわかる場合は、 const
修飾子を使用して、 コンパイル時定数 (compile time constants) としてマークすることができます。
このようなプロパティは、次の要件を満たす必要があります:
- トップレベルまたは object宣言かコンパニオンオブジェクトのメンバ
- String 型の値またはプリミティブ型で初期化される
- カスタムゲッターが無い
コンパイラは定数の使用をインライン化してこのプロパティへの参照を実際の値に置き換えます。 しかし、フィールドが削除される訳では無く、だからリフレクションを使ってやり取り出来ます。
このようなプロパティは、アノテーションに使う事も出来ます:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
遅延初期化プロパティと変数
通常、非nullable型として宣言されたプロパティは、コンストラクタ内で初期化される必要があります。 しかし、これが便利では無いケースもしばしば見られます。 たとえば、プロパティがDI(dependency injection; 依存性注入, 訳注:参考)で初期化されたり、 またはユニットテストのセットアップメソッドで初期化する場合などです。 この手の場合、非nullableのイニシャライザをコンストラクタ内で提供することができませんが、それでもなおクラス内の本体にあるプロパティを参照する際にnullチェックを避けたいでしょう。
このようなケースに対応するには、lateinit
修飾子でプロパティをマークすると良いでしょう:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接参照する(dereference directly)
}
}
この修飾子は、クラス本体(プライマリコンストラクタでない)の中で宣言され、カスタムゲッターやカスタムセッターを持たない var
プロパティ、
トップレベルのプロパティ、ローカル変数などで使用することができます。
プロパティや変数の型が非nullableでなくてはならず、かつプリミティブ型であってはなりません。
lateinit
プロパティが初期化される前にアクセスした場合、アクセスされたプロパティがどれかがわかり、それが初期化されていないことを示すような特別な例外が投げられます。
lateinit var
が初期化されたかどうかをチェックする
lateinit var
がすでに初期化されたかをチェックするには、そのプロパティのリファレンスに対して.isInitialized
を使います:
if (foo::bar.isInitialized) {
println(foo.bar)
}
このチェックはレキシカルにアクセス可能な所でだけ使えます。 同じ型内、外側(outer)の型、または同じファイルのトップレベルなどという事です。
プロパティのオーバライド
プロパティのオーバライド を参照してください。
委譲プロパティ (Delegated Properties)
プロパティのうち最も一般的なのは、単純にバッキングフィールドからの読み込み(そしてもしかしたら書き込み)ですが、 カスタムゲッターとセッターを使えばプロパティの振る舞いを如何様にも実装できます。 この前者の簡単なケースと後者のなんでも出来るの間には、 プロパティでどんな事をしたいかについての確立された共通パターンがあります。 いくつかの例を挙げます:遅延評価値、与えられたキーでのmapの読み込み、データベースへのアクセス、アクセスをトリガとするリスナへの通知等。
このような共通の振る舞いは、委譲プロパティ (delegated properties) を使ってライブラリとして実装することができます。