元の記事 → Swift to Kotlinチートシート
コンストラクターの書き方は色々ある。
いくつか制約があり、適当に書くとコンパイル通らないことが多い。
基本形
class Boo() {}
クラス名の後に書く()が基本となるプライマリコンストラクター。
特に引数がなければ省略して以下のようにも書ける(引数なしコンストラクターが自動生成される?)
class Boo {}
プライマリーコンストラクターは必ず実行されなければならない。
別のコンストラクター(後述のセカンダリーコンストラクター)を書く場合は、その中でthisキーワードを使ってプライマリーコンストラクターを呼ぶ必要がある。
※プライマリーコンストラクターを明示的に記載しない場合は、セカンダリーコンストラクターで呼び出す必要はない
引数をとる
class Foo(bar: String, baz: String) {
val hoge = bar
val piyo = baz
}
コンストラクターに引数を設けた形。引数はメンバー変数につっこむことができる。
メンバー変数の定義は省略して以下の形でも書ける。Typescript同様。
class Foo(val hoge: String, val piyo: String) {}
初期化処理をする
class Foo(bar: String, baz: String) {
val hoge = bar
val piyo = baz
init {
// 初期化処理
}
}
初期化ブロック(init)によって初期化処理を行うことができる。
初期化ブロックはプライマリーコンストラクタ、セカンダリーコンストラクタどちらを実行した場合でも実行される。
セカンダリーコンストラクタを実行した場合は、初期化ブロックが実行された後にセカンダリーコンストラクタの処理が実行される。
class Foo {
init { }
init { }
init { }
}
ちなみに初期化ブロックは複数書けるが、あまり使い所はなさそう。
初期化ブロック(init)ではプロパティが初期化されていない場合がある
class Foo {
init { println(flag) }
val flag = true
init { println(flag) }
}
上記を実行すると、以下の出力になる。
I/System.out: false
I/System.out: true
flagには初期値としてtrueをセットしているが、一つ目のinitではまだ初期化が行われておらずfalseになっている。
init のタイミングでは init より上に書かれたプロパティしか初期化されていないため、initはプロパティ宣言の下に書くのが無難。
セカンダリコンストラクタ
class Hoge(foo: String) {
constructor(foo: String, bar: String): this(foo) {}
}
2つめ以降のコンストラクターは constructor キーワードで書き、セカンダリコンストラクターという。
セカンダリコンストラクターでは必ずプライマリーコンストラクターを呼ぶ必要があり、this キーワードを使って呼び出す。(上記の例では、: this(foo) を書かないとエラーになる)
class Baz {
constructor()
}
上記のようにセカンダリのみ書くこともできるが、あまり意味はない。
ちなみにセカンダリコンストラクターは何も処理がなければ {} を省略できる。
継承一番シンプル
ここから継承するパターン。
open class Bar {}
class Foo: Bar() {}
親クラスにopen修飾子をつけないと継承できない。
親のコンストラクターを呼び出す必要があるので、BarではなくBar()にする必要がある。
親のコンストラクターを呼ぶ
親のコンストラクターはクラス名の定義の後に()で書く方法の他、superで呼び出す方法がある。
open class Base {}
class Foo: Base {
constructor(): super()
}
ただし、上記のように親のコンストラクターに引数がない場合、superを省略して書ける。
open class Base {}
class Foo: Base {
constructor()
}
AndroidのViewを継承する場合
class CustomView: View {
init {
// 全コンストラクターで共通の初期化処理
}
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr)
}
initとセカンダリーコンストラクター実行順序
init
とセカンダリーconstructor
の実行順序は、init
が先でconstructor
が後になる。
class Foo {
init {
// 先に実行される
}
constructor() {
// 後に実行される
}
}
継承がある場合、親クラスのinit
、constructor
が実行されてから、子クラスのinit
、constructor
が実行される。
class Sub: Base {
init { /* 3 */ }
constructor() { /* 4 */ }
}
open class Base {
init { /* 1 */ }
constructor() { /* 2 */ }
}
また、プライマリーコンストラクターは init
よりさらに先に実行され、init
ブロック内でプライマリーコンストラクターに引き渡された値を使うことができる。
class Foo(i: Int) {
init {
println(i)
}
}
privateなコンストラクター
class Foo private constructor () {}
クラス名の後に private constructor が続くのが今までの書き方と比べて特殊に感じそうだが、public なコンストラクターは () の前にある constructorが省略されている形であることを知るとあまり違和感がない。
class Foo() {}
これは下記の省略形である。
class Foo constructor() {}
親クラスの初期化時に呼び出すメソッドを、子クラスでoverrideしてはいけない
クラスのコンストラクターや初期化ブロック(init)が実行されているタイミングでは、子クラスのプロパティーはまだ初期化せれておらず、nullや0やfalseが入った状態になっている。
以下の例では親クラスのinitでviewDidLoadを呼び、子クラスでoverrideしているが、viewDidLoadが実行されるタイミングではまだtextプロパティが初期化されていない。
open class UIViewController {
init { viewDidLoad() }
open fun viewDidLoad() {}
}
class FooViewController: UIViewController() {
val text: String = "Hello World"
override fun viewDidLoad() {
super.viewDidLoad()
println(text) // textはまだ値が入っておらず、nullになっている
}
}