Android Studio 3.0を使い、最近話題のプログラミング言語「Kotlin」の特徴を解説する連載。今回は、「継承」など、「クラス」周りの機能を扱っていきます。また、継承に関連して「スマートキャスト」機能についても解説します。
Android Studioを使い、Kotlin言語の特徴を解説する本連載「Android Studioで始めるKotlin入門」。連載第5回目の今回は、前回に続き、「継承」など、「クラス」周りの機能を扱っていきます。また、継承に関連して「スマートキャスト」機能についても解説します。
Kotlinのクラス継承は基本的にJavaの考え方を踏襲していますが、幾つか異なる点も存在します。
Kotlinのクラスは全て「Any」というクラスを継承しています。Kotlinのクラスは全て非Null型のため、Null許容型であるJavaのObjectのサブクラスとなることはできません。そのため、非Null型として定義されたAnyクラスがKotlinにおける全てのクラスのスーパークラスとなっています。AnyのさらにスーパークラスとしてNull許容型である「Any?」型が存在するという関係になります。
AnyクラスはJavaの「Object」クラスとは別物ですが、JavaのObjectと同じく「toString()」「hashCode()」「equals()」というメソッドを持っています。また相互運用性のため、KotlinからJavaクラスを呼び出す際は、JavaのObjectは自動的に「Any!」型として扱われます。
Kotlinでのクラス継承の書き方を見てみましょう(リスト1)。
//基底クラスAnimal。openを付けないと継承できない open class Animal{} //Animalを継承したCatクラス。「extends」ではなく「:」で基底クラスを指定する class Cat: Animal(){}
ポイントは2つで、まずKotlinのクラスはデフォルトでJavaのfinal(継承不可)の扱いとなっているため、基底クラスでは「open」というキーワードを付けて継承を許可しなければなりません。そして、継承するクラスではJavaの「extends」キーワードではなく、「:」(コロン)で基底クラスを指定します。C++やC#っぽい書き方ですね。
続いて基底クラスのコンストラクタ呼び出しについて、リスト2を見てみましょう。
//基底クラス open class Person(val firstName: String, val lastName: String = ""){……} //継承クラス。継承元のコンストラクタを指定 class BussinessPerson(firstName: String, lastName: String = "", val age: Int) : Person(firstName, lastName){……}
「Person」クラスのプライマリコンストラクタでは「firstName」「lastName」の2つのプロパティが定義されています。それを継承したBussinessPersonクラスのコンストラクタでは、firstName、lastNameの2つの引数に加えてageプロパティを定義しています。そして、firstName、lastName引数については、基底クラスであるPersonクラスのコンストラクタに渡しています。
なお、プライマリコンストラクタが存在せず、セカンダリコンストラクタだけの場合はリスト3のように「super」キーワードを使って基底クラスのコンストラクタを呼び出します。
//プライマリコンストラクタのないクラス class Derived : Base{ //セカンダリコンストラクタはsuperキーワードで直接基底クラスのコンストラクタを呼び出す constructor(a: Any) : super(a){ } //同上 constructor(a: Any, b: Any) : super(a, b){ } }
繰り返しとなりますが、セカンダリコンストラクタから直接基底クラスのコンストラクタを呼び出すのは、そのクラスにプライマリコンストラクタを定義しない場合のみです。プライマリコンストラクタがある場合は、前回解説した通り、セカンダリコンストラクタは最終的に必ずプライマリコンストラクタを呼び出す必要があります。
メソッドのオーバーライドについては、リスト4のようになります。
//基底クラス open class Person(val firstName: String, val lastName: String = ""){ //「open」キーワードでオーバーライド可能に open fun print(){ println("お名前: ${firstName} ${lastName}") } } //継承クラス。継承元のコンストラクタを指定 class BussinessPerson(firstName: String, lastName: String = "", val age: Int) : Person(firstName, lastName){ //メソッドのオーバーライド override fun print(){ println("お名前: ${firstName} ${lastName}, 年齢: $age") } } …… var bussinessPerson = BussinessPerson("Tanaka", "Taro", 22) bussinessPerson.print() //「お名前: Tanaka Taro, 年齢: 22」
Kotlinのメソッドはクラスと同様にデフォルトではオーバーライド不可のため、基底クラスで「open」キーワードを使ってメソッドをオーバーライド可能にしておく必要があります。また、継承先では「override」キーワードで、オーバーライドすることを明示的に宣言しなければなりません。
「デフォルトでクラスもメソッドも継承不可になっている」点がJavaとちょっと違う部分ですが、デフォルトを厳しめにすることで、継承に関わるさまざまなトラブルを避けられるようになっています。例えば、オーバーライドしたつもりがミスタイプしていた、なんてことも避けられますね。
Javaでも最近は@Overrideアノテーションを付けることで、同様のチェックが行えるようになっていますが、Kotlinでは言語レベルでサポートされています*1。
*1)C#なども同様の仕組みを取っています。
『Effective Java』(柴田芳樹 訳、丸善出版)というJavaプログラマー必見の名著がありますが、その「Item 17」では「継承のために設計および文書化する、そうではないなら継承を禁止する」という項目があり、オーバーライド可能なメソッドを絞り込むことなどが提案されています。
Kotlinでは、この原則に従い、意図せず継承可能なクラス、オーバーライド可能なメソッドを作り込んでしまわないよう、デフォルトで継承不可になっているようです。
抽象クラスと抽象メソッドについてはリスト5のようになります。
//抽象クラス abstract class Vehicle{ //抽象メソッド abstract fun run() } //抽象クラスの具象化 class Car : Vehicle(){ //抽象メソッドのオーバーライド override fun run(){ println("Running!") } } …… //抽象クラスはインスタンス化できない val vehicle = Vehicle() //→コンパイルエラー //継承したクラスのインスタンス化と呼び出し val car = Car() car.run()
オーバーライド時にoverrideキーワードが必要な以外は、Javaとほぼ同様なので、細かな解説は省略します。
Copyright © ITmedia, Inc. All Rights Reserved.