【Kotlin】コンストラクタ　−　プライマリとセカンダリ

プライマリコンストラクタとセカンダリコンストラクタについて解説します。

公式リファレンス：[Classes and Inheritance - Kotlin Programming Language](https://kotlinlang.org/docs/reference/classes.html#constructors)

## 対象読者

基本的なクラスの定義方法を知っている方。

## プライマリコンストラクタ

基本的なクラスの定義は次のようになります。

```Kotlin
class MyClass( ... ) { ... }
```

`( ... )` がコンストラクタの定義です。

クラス定義のブロック `{ ... }` の外に定義するコンストラクタを
（後で出てくるセカンダリコンストラクタと区別して）
プライマリコンストラクタと呼びます。

### 省略なし

実は先ほどの書式は省略形です。
省略しなければ次のようになります。

```Kotlin
class MyClass constructor( ... ) { ... }
```

プライマリコンストラクタに可視性やアノテーションを指定する場合はこの省略しない書式を使います。

```Kotlin
class MyClass @Deprecated("Use YourClass class.") private constructor() {}
```

### もっと省略

プライマリコンストラクタに引数がない場合は `()` を省略することができます。

```Kotlin
class MyClass { ... }
```

ちなみに `constructor` と書く書式の場合、引数がなくても `()` は省略できません。

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

Kotlin では引数にデフォルト値を設定できるため、
多くの場合コンストラクタは1つで事足りることでしょう。
ですが、複数のコンストラクタを定義することもできます。

プライマリコンストラクタ以外のコンストラクタは
セカンダリコンストラクタと呼ばれます。
セカンダリコンストラクタはクラス定義のブロック `{ ... }` の内に定義します。

```Kotlin
class MyClass {
    constructor( ... ) { ... } // セカンダリーコンストラクターの1つ
    constructor( ... ) { ... } // セカンダリーコンストラクターの1つ
}
```

`constructor( ... )` の後の `{ ... }` は、空なら省略できます。

セカンダリコンストラクタでは引数がなくても `()` を省略することはできません。

### 他のコンストラクタを呼び出す

セカンダリコンストラクタは他のコンストラクタを呼び出すことができます。
そのためには、`constructor( ... )` の後に `: this( ... )` を付けます。

```Kotlin
class MyClass(int: Int) {
    // プライマリコンストラクタが呼び出されたときに実行される処理。
    init {
        println(int)
    }

    constructor(short: Short) : this(short + 1)
    // ↑引数が Int 型であるコンストラクタ（プライマリコンストラクタ）を呼び出す。
    constructor(byte: Byte) : this((byte + 1).toShort()) // 引数
    // ↑引数が Short 型であるコンストラクタ（もう1つのセカンダリコンストラクタ）を呼び出す。
}

fun main() {
    MyClass(0) // > 0
    MyClass(0.toShort()) // > 1
    MyClass(0.toByte()) // > 2
}
```

セカンダリコンストラクタは必ず、直接的または間接的に、プライマリコンストラクタを呼び出さなければなりません。

## プライマリコンストラクタの `()` 省略すると

プライマリコンストラクタの説明において、次のように述べました。

> プライマリコンストラクタに引数がない場合、`()` を省略することができます。

これは、セカンダリコンストラクタがない場合には正しいです。

ですが、セカンダリコンストラクタがある場合には
`()` を省略すると**プライマリコンストラクタがなくなります**。

プライマリコンストラクタがない場合、
セカンダリコンストラクタには `: this( ... )` がなくても構いません。

```Kotlin
class MyClass {
    constructor(int: Int) : this() // コンパイルエラー！
    constructor(int: Int) // 他のセカンダリコンストラクタを呼び出す必要がなければ `: this( ... )` を書かない
    constructor(short: Short) : this(short.toInt())
}

fun main() {
    MyClass() // コンパイルエラー！
}
```

## コンストラクタ引数の `var` `val`

コンストラクタ引数に `var` もしくは `val` を付けることで、
その引数をプロパティにすることができます。

```Kotlin
class MyClass(
    val userId: String,
    var age: Int
)
```

これは次と等価です。


```Kotlin
class MyClass(
    userId: String,
    age: Int
) {
    val userId: String = userId
    var age: Int = age
}
```

ただしこれができるのはプライマリコンストラクタだけです。

考えてみればこれは当然です。
どのセカンダリコンストラクタからインスタンスを生成したかによってプロパティの有無が変わっては困ります。

## まとめ

- クラス定義のブロックの外に書くのがプライマリコンストラクタ、内に書くのがセカンダリコンストラクタ。
- プライマリコンストラクタでは `constructor` の記述は省略できる。
    - プライマリコンストラクタに可視性やアノテーションを指定する場合は省略できない。
- プライマリコンストラクタを明示しなかった（`constructor` も `( ... )` も書かなかった）場合、
    - セカンダリコンストラクタがなければ引数なしのプライマリコンストラクタが作られる。
    - セカンダリコンストラクタがあればプライマリコンストラクタは作られない。
- セカンダリコンストラクタは `: this( ... )` でプライマリコンストラクタや他のセカンダリコンストラクタを呼び出すことができる。
- プライマリコンストラクタがある場合、セカンダリコンストラクタは直接的もしくは間接的にそれを呼び出す必要がある（`: this( ... )` を書く必要がある）。
- コンストラクタ引数に `var` `val` を付けてプロパティにできる。
    - これができるのはプライマリコンストラクタだけ。