継承
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 // どんな値にもセットされうる
}
派生クラスの初期化の順番
継承したクラスのインスタンスを新しく作る時には、 基底クラスの初期化が最初のステップとして行われます(唯一それに先立つのは基底クラスのコンストラクタに渡す引数の評価だけです)。 つまり、基底クラスの初期化は派生クラスの初期化のロジックが走るよりも前に起こる、という事です。
このことは、基底クラスのコンストラクタが実行される時には、
派生クラスで宣言されたりオーバーライドされたプロパティはまだ初期化されてない、という事を意味します。
それらのプロパティなどを基底クラスの初期化処理で使う事は、
直接的であれ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
のように:
オーバーライドのルール
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()の呼び出し
}
}
Rectangle
と Polygon
の両方から継承するのは問題ありませんが、
どちらも draw()
の実装を持っているため、
Square
ではdraw()
をoverrideする必要があります。
独立した実装を提供する事で曖昧さを無くさなくてはいけません。