プロパティを宣言する
Kotlin では、var を使用して変更可能なプロパティを宣言できます。また、val を使用して読み取り専用プロパティを宣言することもできます。
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
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
}
ゲッターとセッター
完全な属性宣言ステートメントは次のとおりです。
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
ここで、初期化値、ゲッター、セッターはオプションです。システムが初期化値 (またはゲッターの戻り値) からプロパティの型を推測できる場合、プロパティの型もオプションになります。次に例を示します。
var allByDefault: Int? // 错误: 需要明确初始化值, 隐含着默认的 getter and setter
var initialized = 1 // 可推导出数据类型为 Int, 含有默认的 getter and setter
読み取り専用プロパティや変更可能なプロパティの完全な宣言とは異なり、セッターはありません。
val simple: Int? // 有属性类型 Int, 默认的 getter, 必须在构造函数中初始化
val inferredType = 1 // 属性类型为 Int 并有一个默认的 getter
ゲッターとセッターをカスタマイズすることもできます。プロパティの右側で一般関数を宣言するだけです。
var stringRepresentation: String
get() = this.toString()
set(value) {
...
}
Kotlin の文法仕様では、setter のパラメータ名は value が一般的ですが、もちろん別の名前でパラメータを定義することもできます。
Kotlin 1.1 以降、プロパティの型がゲッターから推論できる場合は、プロパティの型の宣言を宣言時に省略できます。
val isEmpty get() = this.size == 0 // Boolean
デフォルトの実装を変更せずに、アクセス許可を変更したり、ゲッターまたはセッターに注釈を追加したりするだけの場合は、関数本体を次のようにしておくこともできます。
var setterVisibility: String = "abc"
private set // 将 setter 修改为 private 权限
var setterWithAnnotation: Any? = null
@Inject set // 为 setter 添加注解
バッキングフィールド
Kotlin のクラスにはフィールドを含めることはできません。ただし、カスタム アクセサーを使用する場合は、バッキング フィールドが必要になる場合があります。したがって、Kotlin は、キーワード フィールドを通じてアクセスできる自動バッキング フィールドを提供します。
var counter = 0 // 初始化值被直接写给 backing fields
set(value) {
if (value >= 0) field = value
}
注: field キーワードは属性アクセスにのみ使用できます。
属性が少なくとも 1 つのデフォルト アクセサーを使用する場合、または属性がカスタム アクセサー内のフィールドを宣言する場合、システムはそのバッキング フィールドを自動的に生成します。
たとえば、次の例にはバッキング フィールドがありません (isEmpty は読み取り専用でセッターがなく、カスタム ゲッターでフィールドが宣言されていないため)。
val isEmpty: Boolean
get() = this.size == 0
val を var に置き換えると、カスタムゲッターはフィールドを宣言しませんが、isEmpty プロパティにはデフォルトのセッターがあるため、バッキングフィールドが存在します。
var isEmpty: Boolean = true
get() = this.size == 0
上記の結論は次のように証明できます。
fun main(args: Array<String>) {
var test: Test = Test()
var fields: Array<Field> = test.javaClass.declaredFields ;
for(field in fields){
println(field.name)
}
}
class Test {
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
var isEmpty: Boolean = true
get() = 1 == 0
val isNumber: Boolean
get() = 1 == 0
}
印刷結果:
counter
isEmpty
バッキングプロパティ
いくつかの操作を実行したいが、それらの操作がバッキング フィールドの性質に適していない場合は、次のようにすることができます。
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
// 这里的操作不希望属于 backing filed,
if (_table == null) {
_table = HashMap()
}
return _table ?: throw AssertionError("Set to null by another thread")
}
これは、Java のプライベート プロパティのデフォルトのゲッターとセッターに似ているため、このプロパティの操作に他の関数が介入することはできません。
コンパイル時定数
コンパイル時に値が決定できる属性は、const を使用してコンパイル時の定数であることを示すように変更できます。このような属性は、次の条件を満たす必要があります。
- オブジェクトの最上位プロパティまたはメンバー
- String型またはプリミティブ型で初期化
- カスタムゲッターはありません
コンパイル時定数はアノテーションで使用できます。
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
遅延初期化プロパティ
一般に、プロパティが null ではないと宣言されている場合は、コンストラクターで初期化する必要があります。しかし、これは非常に不便です。たとえば、プロパティは、アノテーションまたは単体テストのメソッドを介して初期化される場合があります。このような場合、コンストラクターで初期化を実装することはできませんが、プロパティが存在するクラス内で (つまり、null 以外をチェックせずに) 安全にプロパティを使用する必要があります。
この状況を解決するには、lateinit を使用してこのプロパティを装飾します。
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
lateinit を使用するには次の条件を満たす必要があります。
- プライマリ コンストラクターではなくクラス本体で var を使用して宣言された属性
- カスタムのゲッターやセッターはありません
- プロパティタイプがnullではありません
- プリミティブ型にすることはできません
初期化される前に lateinit プロパティにアクセスすると、例外がスローされます。
kotlin.UninitializedPropertyAccessException: lateinit property subject has not been initialized
属性を上書きする
第 6 章を参照
委任されたプロパティ
最も一般的なプロパティは、単にバッキング フィールドから読み取られます (場合によってはバッキング フィールドに書き込まれます)。一方、カスタムの getter メソッドと setter メソッドを使用して、プロパティのあらゆる動作を実現できます。その間に、プロパティの動作に関する一般的なパターンがいくつかあります。例: 遅延値、キーに応じたマップからの値の読み取り、データベースへのアクセス、リスナーへのアクセスの通知など。
これらの一般的な動作は、委任されたプロパティを使用してライブラリとして実装できます。