Edit Page

プロパティ

プロパティの宣言

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)を実装出来ます)。 ここでは、カスタムゲッターの例を示します:

//sampleStart class Rectangle(val width: Int, val height: Int) { val area: Int // ゲッターの戻りの型から推測出来るので、このプロパティの型はオプショナルです get() = this.width * this.height } //sampleEnd fun main() { val rectangle = Rectangle(3, 4) println("幅=${rectangle.width}, 高さ=${rectangle.height}, 面積=${rectangle.area}") }

ゲッターから推測出来る場合はプロパティの型は省略出来ます:

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) としてマークすることができます。 このようなプロパティは、次の要件を満たす必要があります:

コンパイラは定数の使用をインライン化してこのプロパティへの参照を実際の値に置き換えます。 しかし、フィールドが削除される訳では無く、だからリフレクションを使ってやり取り出来ます。

このようなプロパティは、アノテーションに使う事も出来ます:

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) を使ってライブラリとして実装することができます。