Edit Page

継承

Kotlinの全てのクラスは共通の Any スーパークラスをもちます。これはスーパータイプの宣言がないクラスのデフォルトのスーパークラスです。

class Example // Anyから暗黙の継承

Any は三つのメソッド、 equals()hashCode()toString() を持ちます。 つまり、これらのメソッドはすべてのkotlinのクラスで定義されています。

デフォルトでは、Kotlinのクラスは final です。それらは継承出来ません。 クラスを継承可能にするためには、openキーワードでマークする必要があります。

open class Base // クラスは継承可能(継承に関してオープンである)

クラスヘッダ内のコロンの後に型を書くと、明示的にスーパータイプを宣言できます:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

もし継承したクラスがプライマリコンストラクタをもつなら、 基底の型をプライマリコンストラクタの引数を使用して、プライマリコンストラクタで初期化できる(し、しなければいけません)。

もし継承したクラスがプライマリコンストラクタを持たないならば、セカンダリコンストラクタはそれぞれ基底の型を super キーワードを使って初期化するか、他のsuperキーワードを使って初期化してくれるコンストラクタに委譲しなければいけません。 この場合は異なるセカンダリコンストラクタはそれぞれ異なる基底の型のコンストラクタを呼び出しても構いません:

class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
    }
}

メソッドのオーバーライド

Kotlinはオーバーライド可能なメンバとオーバライドする側の両方に、明示的な修飾(modifier)を必要とします。

open class Shape {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}

class Circle() : Shape() {
    override fun draw() { /*...*/ }
}

Circle.draw()にはoverride 修飾が必要です。 もしなければ、コンパイラは文句を言うでしょう。 もし Shape.fill() のように open 修飾が関数になければ、 サブクラス内で同じシグニチャのメソッドを宣言することは override があっても無くても許されません。 ファイナルクラス(つまり open 修飾を持たないクラス)の中では、メンバのopen修飾は何の意味も持ちません。

override としてマークされているメンバは、それ自身もopenとなります。 すなわち、サブクラス内でオーバライドされる可能性があります。 もし再オーバライドを禁止したければ、 final キーワードを使ってください:

open class Rectangle() : Shape() {
    final override fun draw() { /*...*/ }
}

プロパティのオーバーライド

プロパティのオーバライドもメソッドのオーバライドと同じように動きます。 基底クラスで宣言されているプロパティが継承されたクラスで再宣言される場合は、 overrideをつけなくてはならず、しかも型が互換(compatible)で無くてはなりません。 各宣言されたプロパティは初期化子やgetメソッドでオーバーライド出来ます。

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

val プロパティを var プロパティとしてオーバライドすることは出来ますが、逆は出来ません。 前者が許されるのはval のプロパティは、本質的にgetメソッドを宣言しているためであり、 それを var としてオーバライドすることは、さらにsetメソッドを追加で派生クラスに宣言する事になるだけだからです。

プライマリコンストラクタでのプロパティ宣言でも、 overrideキーワードを使用できることに注意してください:

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // いつも頂点は4つ(訳注:長方形だから)

class Polygon : Shape {
    override var vertexCount: Int = 0  // どんな値にもセットされうる
}

派生クラスの初期化の順番

継承したクラスのインスタンスを新しく作る時には、 基底クラスの初期化が最初のステップとして行われます(唯一それに先立つのは基底クラスのコンストラクタに渡す引数の評価だけです)。 つまり、基底クラスの初期化は派生クラスの初期化のロジックが走るよりも前に起こる、という事です。

//sampleStart open class Base(val name: String) { init { println("基底クラスの初期化") } open val size: Int = name.length.also { println("基底クラスのsizeの初期化: $it") } } class Derived( name: String, val lastName: String, ) : Base(name.replaceFirstChar { it.uppercase() }.also { println("基底クラスへ渡す引数: $it") }) { init { println("継承先クラスの初期化") } override val size: Int = (super.size + lastName.length).also { println("継承先クラスのsizeの初期化: $it") } } //sampleEnd fun main() { println("継承したクラスの作成(\"hello\", \"world\")") Derived("hello", "world") }

このことは、基底クラスのコンストラクタが実行される時には、 派生クラスで宣言されたりオーバーライドされたプロパティはまだ初期化されてない、という事を意味します。 それらのプロパティなどを基底クラスの初期化処理で使う事は、 直接的であれopenメンバの実装を通して間接的にであれ、 おかしな事になったり実行時の失敗(runtime failure)になります。 基底クラスを設計する時には、 コンストラクタ、プロパティの初期化子、initブロックでopenメンバを使う事は避けましょう。

基底クラスの実装の呼び出し

派生クラスのコードは、 基底クラスの関数やプロパティアクセサの実装を、 superキーワードを使って呼び出す事が出来ます:

open class Rectangle {
    open fun draw() { println("長方形を描く") }
    val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("長方形を塗りつぶす")
    }

    val fillColor: String get() = super.borderColor
}

インナークラスの中で外側のクラスの基底クラスにアクセスしたい場合は、 superキーワードに外側のクラスの名前をつけます: super@Outerのように:

open class Rectangle { open fun draw() { println("長方形を描く") } val borderColor: String get() = "black" } //sampleStart class FilledRectangle: Rectangle() { override fun draw() { val filler = Filler() filler.drawAndFill() } inner class Filler { fun fill() { println("塗りつぶし") } fun drawAndFill() { super@FilledRectangle.draw() // 長方形のdraw()の実装の呼び出し fill() println("塗りつぶされた長方形の描画、色は${super@FilledRectangle.borderColor}") // RectangleのborderColorのget()の実装を使用 } } } //sampleEnd fun main() { val fr = FilledRectangle() fr.draw() }

オーバーライドのルール

Kotlinでは、実装の継承は次のルールで定められています: もしクラスが直接の基底クラスから同じメンバの複数の実装を継承する場合、 派生クラスはこのメンバをoverrideし、その独自の実装(おそらく、継承されたものの一つを使用して)を提供しなければいけません。

どの基底型から継承した実装かを示すためには、 superキーワードに基底型を角括弧でつけます。例えば super<Base>のように:

open class Rectangle {
    open fun draw() { /* ... */ }
}

interface Polygon {
    fun draw() { /* ... */ } //  // インタフェースのメンバはデフォルトで'open'
}

class Square() : Rectangle(), Polygon {
   // コンパイラはdraw()をオーバライドする事を要求する
   override fun draw() {
        super<Rectangle>.draw() // Rectangle.draw()の呼び出し
        super<Polygon>.draw() // Polygon.draw()の呼び出し
    }
}

RectanglePolygonの両方から継承するのは問題ありませんが、 どちらも draw() の実装を持っているため、 Squareではdraw()をoverrideする必要があります。 独立した実装を提供する事で曖昧さを無くさなくてはいけません。