Kotlinのinitブロックとsuperの呼び出しは、クラスの階層を考慮したオブジェクトの正しい初期化を担当します。これは構造と継承に影響を与える基本的なメカニズムです。
Javaでは、コンストラクタを明示的にsuper(...)を使って呼び出すことも、暗黙のうちにデフォルトで行うこともできます。しかしKotlinでは、主コンストラクタと副コンストラクタの明確な分離があり、initブロックが初期化に使用されます。
主な難しさは、プロパティが初期化されるタイミング、initブロックが呼び出されるタイミング、特にクラスの継承や主/副コンストラクタの組み合わせ時にコンストラクタ呼び出しの順序を理解することです。
コード例:
open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // 出力: // Base init: Ivan // Derived init: Ivan, 25
重要な特徴:
継承チェーン内のinitブロックはどの順序で実行されますか?
まずスーパークラスのinitブロックが実行され、そのプロパティが初期化され、その後サブクラスのinitブロックがその自身のプロパティの後に実行されます。
コード例:
open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B
Kotlinでコンストラクタからでなくsuper()を呼び出すことはできますか?
いいえ、スーパークラスの呼び出しは、スーパークラスのコンストラクタへのパラメータを提供することを伴うコンストラクタの宣言の一部としてのみ実行されます。
Kotlinで副コンストラクタが主を呼び出さなくてもよいですか?
いいえ、すべての副コンストラクタは他の副コンストラクタまたは主コンストラクタへの呼び出しを委任する必要があります。主コンストラクタはスーパークラスのコンストラクタを呼び出します。
開発者がスーパークラスのinitブロック内でサブクラスの初期化されるプロパティにアクセスしようとした場合:
open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **利点:** - ポリモーフィズムの使用。 **欠点:** - プロパティsはまだ初期化されておらず、スーパークラスのinit呼び出し時に結果はnullまたはエラーになります。 ## ポジティブケース 初期化を分離してコンストラクタからオーバーライドされたメソッドまたはプロパティを呼び出さないようにする: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **利点:** - 初期化されていないデータへのアクセスがない; - 初期化の順序が保証されます。 **欠点:** - 正しい初期化のためにコード量が増加します。