Edit Page

プロパティとフィールド

プロパティの宣言

Kotlinのクラスは、プロパティを持つことができます。 これらは、 var キーワードを使用して、ミュータブル(可変)として宣言することもでき、 val キーワードを使用するとイミュータブル(読み取り専用)にすることもできます。

public class Address { 
  public var name: String = ...
  public var street: String = ...
  public var city: String = ...
  public var state: String? = ...
  public var zip: String = ...
}

プロパティを使うにはJavaでのフィールドでやるように、ただ単純に名前で参照するだけで良いです:

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>]

イニシャライザ、ゲッターとセッターは必須ではありません。イニシャライザか基本クラスのメンバーからオーバライドされることが推測される場合は、プロパティの型も必須ではありません。

例:

var allByDefault: Int? // エラー:明示的なイニシャライザが必要、デフォルトのゲッターとセッターは暗黙
var initialized = 1 // これは Int 型を持ち、ゲッターとセッターも持つ

読み取り専用のプロパティ宣言の完全な構文は、ミュータブルのものと比べて2点異なります。var の代わりに val で始まるのと、セッターを認めないことでです:

val simple: Int? // Int 型を持ち、デフォルトゲッターを持つ。コンストラクタ内で初期化が必要
val inferredType = 1 // Int 型を持ち、デフォルトゲッターを持つ

カスタムアクセサは普通の関数ととても似ていて、プロパティの中に宣言することができます。ここでは、カスタムゲッターの例を示します:

val isEmpty: Boolean
  get() = this.size == 0

カスタムセッターは次のようになります:

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のクラスは、フィールドを持つことができません。しかし、カスタムアクセサを使用するときにバッキングフィールドが必要になることがあります。この目的のために、Kotlinは自動バッキングフィールドを提供します。これにより、 field 識別子を使用してアクセスすることができます。

var counter = 0 // イニシャライザの value はバッキングフィールドへ直に書き込まれる
  set(value) {
    if (value >= 0)
      field = value
  }

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をセットされた")
  }

全ての点において、これはちょうどJavaと同じです。なぜなら、privateプロパティへデフォルトゲッターとセッターでのアクセスが、関数呼び出しのオーバヘッドが無いように最適化されているためです。

コンパイル時定数

値がコンパイル時にわかるプロパティは、 const 修飾子を使用して、 コンパイル時定数 (compile time constants) としてマークすることができます。 このようなプロパティは、次の要件を満たす必要があります:

  • トップレベルまたは object のメンバ
  • String 型の値またはプリミティブ型で初期化される
  • カスタムゲッターが無い

このようなプロパティには、アノテーションを付けることができます:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

遅延初期化プロパティ

通常、非null型として宣言されたプロパティは、コンストラクタ内で初期化される必要があります。 しかし、かなり多くの場合において、これは便利ではありません。 たとえば、プロパティは、依存オブジェクト (DI; dependency injection; 依存性注入, 訳注:参考)を介して、またはユニットテストのセットアップメソッドで初期化することができます。 この事例では、非nullのイニシャライザをコンストラクタ内で提供することができませんが、それでもなおクラス内の本体にあるプロパティを参照する際にnullチェックを避けたいでしょう。

このような事例を扱うには、lateinit 修飾子でプロパティをマークすると良いでしょう:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // 参照先を直に見に行く(dereference directly)
    }
}

この識別子はクラス本体(プライマリコンストラクタでない)の中で宣言され、カスタムゲッターやカスタムセッターを持たない var プロパティでのみ使用することができます。 プロパティの型が非nullかつ、プリミティブ型であってはなりません。

lateinit プロパティが初期化される前にアクセスした場合、アクセスされたプロパティと、それが初期化されていないことを特定するための特別な例外が投げられます。

プロパティのオーバライド

メンバのオーバライド を参照してください。

委任プロパティ (Delegated Properties)

プロパティのうち最も一般的なのは、単純にバッキングフィールドからの読み込み(または書き込みかもしれない)です。 一方、カスタムゲッターとセッターを使えばプロパティの振る舞いを如何様にも実装できます。 いろんなところに、プロパティの動作について、確立された共通パターンがあります。 いくつかの例を挙げます:遅延評価値、与えられたキーでのmapの読み込み、データベースへのアクセス、アクセスをトリガとするリスナへの通知等。

このような一般的な振る舞いは、委譲プロパティ (delegated properties) を使ってライブラリに実装することができます。