Kotlinチートシート: クラス＆コンストラクター編

元の記事 → [Swift to Kotlinチートシート](https://qiita.com/alt_yamamoto/items/d545309093baa53a539e)

コンストラクターの書き方は色々ある。
いくつか制約があり、適当に書くとコンパイル通らないことが多い。

# 基本形
```Kotlin
class Boo() {}
```
クラス名の後に書く()が基本となるプライマリコンストラクター。
特に引数がなければ省略して以下のようにも書ける（引数なしコンストラクターが自動生成される？）

```Kotlin
class Boo {}
```
プライマリーコンストラクターは必ず実行されなければならない。
別のコンストラクター（後述のセカンダリーコンストラクター）を書く場合は、その中でthisキーワードを使ってプライマリーコンストラクターを呼ぶ必要がある。
※プライマリーコンストラクターを明示的に記載しない場合は、セカンダリーコンストラクターで呼び出す必要はない

# 引数をとる

```Kotlin
class Foo(bar: String, baz: String) {
    val hoge = bar
    val piyo = baz
}
```
コンストラクターに引数を設けた形。引数はメンバー変数につっこむことができる。
メンバー変数の定義は省略して以下の形でも書ける。Typescript同様。

```Kotlin
class Foo(val hoge: String, val piyo: String) {}
```


# 初期化処理をする
```Kotlin
class Foo(bar: String, baz: String) {
    val hoge = bar
    val piyo = baz
    init {
        // 初期化処理
    }
}
```
初期化ブロック(init)によって初期化処理を行うことができる。
初期化ブロックはプライマリーコンストラクタ、セカンダリーコンストラクタどちらを実行した場合でも実行される。
セカンダリーコンストラクタを実行した場合は、初期化ブロックが実行された後にセカンダリーコンストラクタの処理が実行される。

```Kotlin
class Foo {
    init { }
    init { }
    init { }
}
```
ちなみに初期化ブロックは複数書けるが、あまり使い所はなさそう。

# 初期化ブロック（init）ではプロパティが初期化されていない場合がある

```Kotlin
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はプロパティ宣言の下に書くのが無難。

# セカンダリコンストラクタ

```Kotlin
class Hoge(foo: String) {
    constructor(foo: String, bar: String): this(foo) {}
}
```

2つめ以降のコンストラクターは constructor キーワードで書き、セカンダリコンストラクターという。
セカンダリコンストラクターでは必ずプライマリーコンストラクターを呼ぶ必要があり、this キーワードを使って呼び出す。（上記の例では、: this(foo) を書かないとエラーになる）


```Kotlin
class Baz {
    constructor()
}
```
上記のようにセカンダリのみ書くこともできるが、あまり意味はない。
ちなみにセカンダリコンストラクターは何も処理がなければ {} を省略できる。

# 継承一番シンプル
ここから継承するパターン。

```Kotlin
open class Bar {}
class Foo: Bar() {}
```
親クラスにopen修飾子をつけないと継承できない。
親のコンストラクターを呼び出す必要があるので、BarではなくBar()にする必要がある。

# 親のコンストラクターを呼ぶ
親のコンストラクターはクラス名の定義の後に()で書く方法の他、superで呼び出す方法がある。

```Kotlin
open class Base {}
class Foo: Base {
    constructor(): super()
}
```
ただし、上記のように親のコンストラクターに引数がない場合、superを省略して書ける。

```Kotlin
open class Base {}
class Foo: Base {
    constructor()
}
```

# AndroidのViewを継承する場合

```Kotlin
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`が後になる。

```Kotlin
class Foo {
    init {
        // 先に実行される
    }
    constructor() {
        // 後に実行される
    }
}
```

継承がある場合、親クラスの`init`、`constructor`が実行されてから、子クラスの`init`、`constructor`が実行される。

```Kotlin
class Sub: Base {
    init { /* 3 */ }
    constructor() { /* 4 */ }
}

open class Base {
    init { /* 1 */ }
    constructor() { /* 2 */ }
}
```
また、プライマリーコンストラクターは `init` よりさらに先に実行され、`init`ブロック内でプライマリーコンストラクターに引き渡された値を使うことができる。

```Kotlin
class Foo(i: Int) {
    init { 
        println(i)
    }
}

```

# privateなコンストラクター

```Kotlin
class Foo private constructor () {}
```
クラス名の後に　private constructor が続くのが今までの書き方と比べて特殊に感じそうだが、public なコンストラクターは () の前にある constructorが省略されている形であることを知るとあまり違和感がない。

```Kotlin
class Foo() {}
```
これは下記の省略形である。

```Kotlin
class Foo constructor() {}
```

# 親クラスの初期化時に呼び出すメソッドを、子クラスでoverrideしてはいけない

クラスのコンストラクターや初期化ブロック（init）が実行されているタイミングでは、子クラスのプロパティーはまだ初期化せれておらず、nullや0やfalseが入った状態になっている。

以下の例では親クラスのinitでviewDidLoadを呼び、子クラスでoverrideしているが、viewDidLoadが実行されるタイミングではまだtextプロパティが初期化されていない。

```Kotlin
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になっている
    }
}
```