KotlinDive

プログラミング言語 Kotlin についての入門ブログです。

クラス(オーバーライド)

メソッド

Kotlinでは、オーバーライド可能なメンバーには明示的に「open」キーワードを付けなければいけません。
また、子クラスで実際にオーバーライドしているメンバーには「override」キーワードを付ける必要があります。


fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    val score = score

    override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
}

Name is suzuki. Score is 82.1.

オーバーライドしたメンバー自身は、自動的に「open」になります。
(上記の例で言えば、Studentクラスのdescribe関数は、Studentの子クラスでオーバーライド可能です)
もし、これ以上、子クラスでオーバーライドしてほしくなければ、「final」キーワードを付けます。 そうすることで、それ以上のオーバーライドを禁じることができます。


fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    val score = score

    // finalをつけるとこれ以上、子クラスでオーバーライドできなくなる
    final override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
}

プロパティ

プロパティもメソッドと同様にオーバーライドすることができます。
プロパティのオーバーライドでは、親クラスで読取り専用の「val」だったものも、子クラスで「var」にオーバーライドスルことができます。
これは、getterしかなかったプロパティにオーバーライドによってsetterを追加することが可能だということです。


fun main(args: Array) {
    val student = Student("suzuki", 82.1)
    student.name = ""

    student.describe()
}

open class Person(name: String) {
    // openをつけることでオーバーライド可能にする
    open val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    override var name = name
        set(value) {
            if (value.isEmpty()) {
                field = "empty"
            } else {
                field = value
            }
        }

    val score = score

    // finalをつけるとこれ以上、子クラスでオーバーライドできなくなる
    final override fun describe() {
        println("Name is ${name}. Score is ${score}.")
    }
}

Name is empty. Score is 82.1.

継承時の初期化の順序

子クラスをインスタンス化した時の、初期化の順番は次のとおりになります。

  1. 親クラスのコンストラクタに渡す引数の評価
  2. 親クラスの記述されている初期化処理
  3. 子クラスに記述されている初期化処理

fun main(args: Array) {
    val student = Student("suzuki", 82.1)
}

open class Person(name: String) {
    init {
        println("Base: init block")
    }

    val name = name.also { println("Base: property initializer. ${it}") }
}

class Student(name: String, score: Double) : Person(name.also { println("Derived: Argument for Base Constructor") }) {
    init {
        println("Derived: init block")
    }

    var score = score.also { println("Derived: property initializer. ${it}") }
}

Derived: Argument for Base Constructor
Base: init block
Base: property initializer. suzuki
Derived: init block
Derived: property initializer. 82.1

super

子クラスで親クラスのメンバーにアクセスして処理を行ったり、値を設定したりしたい場合があります。
そのような場合には、「super」を使って親クラスのメンバーにアクセスします。


fun main(args: Array) {
    val student = Student("suzuki", 82.1)
    student.name = ""

    student.describe()
}

open class Person(name: String) {
    open val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

class Student(name: String, score: Double) : Person(name) {
    override var name = name
        set(value) {
            if (value.isEmpty()) {
                field = "empty"
            } else {
                field = value
            }
        }

    val score = score

    final override fun describe() {
        // superを使って親クラスのメンバーを呼び出す
        super.describe()
        println("Name is ${name}. Score is ${score}.")
    }
}

Name is empty.
Name is empty. Score is 82.1.

複数の実装をもつメンバーのオーバーライドルール

クラスやインターフェースを複数継承した場合に、それぞれの親クラスやインターフェースに 同じ名前のメンバーが別々の実装を持っている場合があります。
このような場合には、子クラスでは必ずそのメンバーをオーバーライドして子クラス独自に処理を実装する必要があります。
(superを使っていずれかの親クラスやインターフェースの処理を採用しても、もちろんOKです)

複数の親クラスやインターフェースを継承した場合は、「super<base>」とすることでそれぞれの親クラスやインターフェースにアクセスします。


fun main(args: Array) {
    val student = Student("suzuki", 82.1)

    student.describe()
}

open class Person(name: String) {
    val name = name

    open fun describe() {
        println("Name is ${name}.")
    }
}

interface Describable {
    fun describe() {
        println("dummy")
    }
}

class Student(name: String, score: Double) : Person(name), Describable {
    val score = score

    // describeの実装が複数存在してしまうため、
    // オーバーライドして独自に実装しなおさないとビルドエラーになる
    override fun describe() {
        // それぞれのdescribeにアクセスできる
        super.describe()
        super.describe()
        
        println("Name is ${name}. Score is ${score}.")
    }
}

Name is suzuki.
dummy
Name is suzuki. Score is 82.1.