Kotlinのきほん クラスの初期化編

はじめに

Kotlinのクラスの初期化についてまとめます。参考にするのは以下の書籍。

Kotlinイン・アクション

Kotlinイン・アクション

チートシートも見ながら進めます。

Kotlinにおける初期化の種類

ここで言う初期化とは、クラスがインスタンス化される時に実行される処理のことです。Javaで言えばコンストラクタがそれに当たります。Kotlinでは以下の種類の初期化処理を実装できます。

プライマリコンストラク

Javaではコンストラクタをいくつも持つことが出来ます。これはKotlinでも同じですが、Kotlinではコンストラクタをプライマリコンストラクタとセカンダリコンストラクタに分けています。

プライマリコンストラクタは以下のような方法で定義します。

class User(val shortName: String)

このかっこ( ... )がプライマリコンストラクタです。プライマリコンストラクタを使うことで、プロパティの宣言と初期化を同時に行うことが出来ます。この処理と同等のものは以下のようになります。

class User constructor(_shortName: String) {

  val shortName: String = _shortName

}

ここで注意して欲しいのは、プライマリコンストラクタがプロパティの宣言と初期化を行うのは、引数にvalもしくはvarが付いている場合のみです。それ以外の場合は単なるコンストラクタの引数として処理されるため、クラスのプロパティとしては宣言されません。

プライマリコンストラクではデフォルト引数を使用することもできます。例えば以下のようにactivate引数の指定がない場合にはデフォルトでfalseにするといったこともできます。

class User(val shortName: String, val activate: Boolean = false)

クラスがスーパークラスを持つ場合、スーパークラスのコンストラクタを明示的に呼び出す必要があります。以下の例ではUserクラスをEmployeeクラスが継承しています。Employeeプライマリコンストラクタの後に続けて書かれているのが、Userクラスのプライマリコンストラクタ呼び出しです。

open class User(val shortName: String, val activate: Boolean = false)
     
class Employee(shortName: String) : User(shortName)

さて、ここまでプライマリコンストラクタの基本的な使い方を書いてきましたが、初期化を行いたい時に特定の処理(イベント通知、バリデーション)などを行いたい場合どうすればいいのでしょうか?そこで登場するのが初期化ブロックです。

初期化ブロック

初期化ブロックを使うことで、クラスを初期化する時に特定の処理を実行できます。

fun main(args: Array<String>) {
    User("bkst")
}

object Logger {

    fun log(msg: String) = println(msg)

}

class User constructor(_shortName: String) {

    // 初期化ブロック
    init {
        Logger.log("User created")
    }

    val shortName: String = _shortName

}

初期化ブロックはクラスの中にいくつ書いても大丈夫です。ただ、実行順番は上から順番に行われるみたいです(我調べ)。

セカンダリコンストラク

KotlinではJavaと違ってコンストラクタを複数持つ状況がありません。なぜなら、上記のプライマリコンストラクタとデフォルト引数を用いれば引数を複数パターン持つ場合をカバーできるからです。

// Javaの場合
public class User {
    
    private String name;

    private String postalCode;

    public User(String name) {
        this(nam, "UnKnown")
    }
    
    public User(String name, String postalCode) {
        this.name = name;
        this.postalCode = postalCode;
    }
}
// Kotlinの場合
public class User(val name: String, val postalCode: String = "Unknown")

しかし、複数のコンストラクタを必要とする場合もあります。それは、コンストラクタ引数の種類が違ったり、引数の数に応じてコンストラクタごとの処理を行いたい等といった場合です。そういったときにはKotlinのセカンダリコンストラクを複数宣言することで解決します。

class User {
    
    constructor(name: String, postalCode: String) {
        ...
    }
    
    constructor(userId: UserId) {
        ...
    }
    
}

セカンダリコンストラクタはconstructorキーワードでメソッドのように定義します。

セカンダリコンストラクタから、他のセカンダリコンストラクタを呼び出すことも可能です。

class User {

    private val postalCode: String

    private val name: String

    constructor(name: String, postalCode: String) {
        this.name = name
        this.postalCode = postalCode
    }

    // 他のコンストラクタ呼び出し
    constructor(userId: String) : this("Unknown", "Unknown") {
        ...
    }
}

おわりに

Kotlinは学ぶたびに奥が深いと実感できる言語です。特に、既存言語(特にJava)で手が届かないようなものが綺麗になっていて気持ちいい。