読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

Kotlinのプロパティとフィールド

※下記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 == 0

http://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 = 0

http://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 > 0

http://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のフィールドではなく、アクセッサーメソッドを使う