クラスの実装 〜 コンストラクタ
前回の記事「クラスの実装 〜 プロパティとバッキングフィールド」では、 次のようなプロパティを1つだけ持つ単純なクラスを通して、プロパティのセッターとゲッター、 それとバッキングフィールドについて学びました。
class Person { var name: String = "" }
この記事では、このクラスを正しく初期化するためのコンストラクタの実装方法を説明します。
コンストラクタ
Kotlin のコンストラクタは constructor という名前で定義します。
下で出てくる、プライマリコンストラクタはクラス名を使えます。
package test class Person { val name: String val id: Int constructor(name: String, id: Int) { this.id = id this.name = name } constructor(id: Int) { this.id = id if(id == 1) { this.name = "Ichiro Suzuki" } else { this.name = "(unknown)" } } }
このクラスを次のように使います。
import test.Person fun main() { val p1 = Person("Kotlin Taro", 123) //ひとつめのコンストラクタが呼ばれる println("${p1.name}, ${p1.id}") val p2 = Person(1) //2つ目のコンストラクタが呼ばれる println("${p2.name}, ${p2.id}") }
すると実行結果は次のようになります。
Kotlin Taro, 123 Ichiro Suzuki, 1
変数 p2 は id として 1 を渡しているだけですが、2つ目のコンストラクタがあるために、name プロパティも正しく設定されています。
プライマリコンストラクタ
Kotlin では class キーワード、クラス名 (今回の場合 Person というクラス名) を記述したところで、いきなりコンストラクタを定義することができます。ここで定義するコンストラクタを、プライマリコンストラクタといいます。
例えば name プロパティを初期化するプライマリコンストラクタは、次のようにかけます。
class Person(var name: String = "") { }
型に続けて書いた = "" はデフォルト値です。
また、このとき、ブレース {} も省略できるので、次のようにかけます。
class Person(var name: String = "")
なんと、1 行です。これも上で書いた場合と同様に次のように使えます。
fun main() { val p = Person("Ichiro") println(p.name) // Ichiro }
もし、上記の name プロパティに関し、 コンストラクタへの入力を必須とし、さらに読み取り専用のプロパティにするには、 val を使います。
class Person(val name: String) fun main() { val p = Person("Mike") // p.name = "Hanako" // 不可 println(p.name) }
クラスの初期化用の init ブロックの利用
クラス定義内では内部フィールドの初期化用に init ブロックが使えます。 プライマリコンストラクタへのパラメータは、init ブロックでアクセス可能です。
従って、プライマリコンストラクタで渡した値の入力チェックなどを行う場合には、 プロパティを別個に作成して、init でコンストラクタへの値をチェックしたのちにセットできます。
例えば、次のようにブランク (空白) のチェックができます。
class Person(name: String) { val name: String init { if (name.isBlank()) { throw Exception("name must not be blank.") } this.name = name } }
ここで文字列が空白でないかどうか確認するために、isBlank() メソッドを使っています。 よく似たメソッドに isEmpty() があります。この違いはなんでしょうか? isEmpty() の場合は "" (長さ 0 の文字列) が true となり、他は false です。 isBlank() の場合は長さが 1 以上の空白文字 " " なども true になります。
自分のインスタンスへのアクセスを明示的に行うには this キーワードを使います。
上記のように Person クラスを実装すると、次のように実行できます。
fun main() { val p = Person("Mike") println(p.name) // "Mike" を出力 }
もし空白文字を渡せば・・・
fun main() { val p = Person(" ") println(p.name) }
次のように例外が投げられました。確かに入力チェックが効いてますね。
Exception in thread "main" java.lang.Exception: name must not be blank. at Person.<init>(main.kt:6) at MainKt.main(main.kt:13) at MainKt.main(main.kt)
セカンダリコンストラクタ
プライマリコンストラクタが定義されているとき、プライマリ以外のコンストラクタは特にセカンダリコンストラクタと呼ばれます。
セカンダリコンストラクタは、プライマリコンストラクタを呼ぶ必要があります。
package test class Person(val name: String, val id: Int) { constructor(name: String): this(name, -1) }
セカンダリコンストラクタからプライマリコンストラクタを呼ぶ際には、この例のように this を使います。