覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotlin - 継承

Javaのクラスでは明示的に final キーワードを付与しない限り、どのようなクラスからでもそのサブクラスを継承して作成可能です。
メソッドもオーバーライドできます。

◾️簡単に継承できるというサンプル

public class Person {

    protected final String name;

    public Person(String name) {
        this.name = name;
    }

    public void myName() {
        System.out.println("My Name is Unknown");
    }
}

public class Tom extends Person {

    public Tom() {
        super("Tom");
    }

    @Override
    public void myName() {
        System.out.println("My name is Tom!!");
    }
}

これは便利なように見えて、壊れやすい基底クラス(fragile base class)という問題を起こしやすくなります。
基底クラス(親クラス)のコードが変更された時に、サブクラスで意図しない挙動を引き起こすことがあるというものです。

サブクラスは基底クラスの作成者が予期していない方法でメソッドをオーバライドしてしまうというリスクが存在します。

Javaプログラミングスタイルの名著と呼ばれている「Effective Java」では、
"継承のために設計及び文書化する。でなければ継承を禁止する" と記述されています。
これは、サブクラスによってオーバライドされる事を意図しない全てのクラスやメソッドは明示的に final と指定するべきである ということを意味しています。

とは書かれていますが、Javaの言語仕様上はデフォルトが継承可能な状態なので、なかなかこの状態は守られることは少ないように感じます。
そして、壊れゆくクラス達が多数出現するのを目にします。


Kotlinではデフォルトは final

Kotlin では、「Effective Java」に記述された思想を受け継いでいます。

Java のクラスやメソッドがデフォルトでサブクラス化やオバーライドが可能なのに対して、
Kotlin の場合、デフォルトがfinalの状態になっています。

あるクラスを継承したサブクラスの作成を許可するためには、そのクラスにopen 修飾子を付ける必要があります。
+ オーバーライドを許可する全てのメソッドやプロパティにも open 修飾子を付ける必要があります。

例えば以下のようなクラスを定義しても継承もメソッドのオーバーライドもできません。

class KotlinPerson(val name : String) {
    fun myName() {
        println("My name is unknown")
    }
}


継承させたい場合は open の付与が必要です

open class KotlinPerson(val name : String) {
    open fun myName() {
        println("My name is unknown")
    }
}


こうすると以下のような継承したサブクラスの作成やメソッドのオーバーライドができます

class Mike(name: String) : KotlinPerson(name) {

    override fun myName() {
        println("My name is Mike")
    }
}

ちなみに、基底クラスやインターフェースのメンバをオーバーライドした場合、オーバーライドしたメンバはデフォルトで open となっています。
自身の実装をさらなるサブクラスでオーバーライド禁止にしたい場合は、そのメンバに final を指定する必要があります。


抽象クラス

Kotlin には Java と同様に abstract キーワードを指定して以下のような抽象クラスを宣言することが可能です。

abstract class Animal {

    // 抽象メソッド。サブクラスでのオーバーライド必須
    abstract fun walk()

    // 非抽象メソッドはデフォルト open ではないが openと付与することもできる
    open fun eat() {
    }

    // 非抽象メソッド。open指定してないのでサブクラスでオーバーライドできる
    fun smell() {
        println("Smell the smell")
    }
}


継承したサブクラスは以下のように定義できます

class Cat : Animal() {
    // 抽象メソッドなのでオーバーライド必須
    override fun walk() {
        println("Cat is walk")
    }

    // 親クラスでopenにしてあったのでオーバライド可能
    override fun eat() {
        super.eat()
        println("The cat is eating deliciously")
    }
}


Kotlin におけるクラスでのアクセス修飾子の意味は以下の通りです

  • final
    • 対応するメンバはオーバーライドできない
    • クラスのデフォルト値
  • open
    • 対応するメンバはオーバライドできる
    • これを付与する事で継承ができるようになる。明示的な指定が必要。
  • abstract
    • 対応するメンバがオーバーライドしなければならない
    • 抽象クラスのみで使用可能。抽象メンバ(抽象メソッド)は実装を持つ事ができない
  • override
    • 対応するメンバの内容をオーバーライドする
    • finalと指定しなければオーバーライドしたメンバはデフォルトで open となる


まとめ

簡単にですが、Kotlinの継承に関わる内容を記述しました。といっても、相変わらす「Kotlinイン・アクション」の写経にすぎませんが。。。
個人的には、Kotlinではデフォルトでクラス(実装クラス)がデフォルトで継承できないというのが良い点だなと思います。
Javaだと共通化という名目のもとに、実装クラスを継承してやたら複雑になってしまったアプリケーションを見る事が少なくありまん。
Kotlinは、これを少しでも防ぐ方向にあるように感じました。