今回の記事からKotlinのオブジェクト指向的な話をしていきます。
Kotlinは高次関数や関数リテラルなど関数型言語的な側面を持ちますが、Kotlinはオブジェクト指向言語であると主張されています。
※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリの8割は参考サイトの翻訳です。残りの2割は私の解釈で加筆や変更を施した構成です。
参考サイト http://confluence.jetbrains.net/display/Kotlin/Classes+and+Inheritance
クラスと主要コンストラクタ
Kotlinでは次のようにクラスを宣言します。
class Circle(x : Double, y : Double, radius : Double) { val x = x val y = y val radius = radius }
最初の行に注目してください。これはCircleクラスの宣言です。このクラスは、Double型のパラメータを3つ取るコンストラクタを持っています。このようなクラスヘッダの直後で宣言されているコンストラクタを主要コンストラクタ(primary constructor)と呼びます。
Circleクラスの新しいインスタンスを生成するために、そのコンストラクタを通常の関数のように呼び出します。
val circle = Circle(0.0, 0.0, 1.0)
(注意:newキーワードは不要。)
コンストラクタの宣言がないクラスはインスタンス化できません。
class MyClass{} val myClass = MyClass() //コンパイルエラー
注意:コンストラクタを持たないクラスは、いかなる状態(不可視フィールドを含むプロパティ)も宣言できない。また、コンストラクタを持つクラスを継承することもできない。
主要コンストラクタは、プロパティのイニシャライザを宣言順に実行します。このイニシャライザはコンストラクタの引数を使用できます。上記の例でコンストラクタが行う唯一のことは、x, y, radius の初期化です。
さらに、クラスの本体に「匿名のイニシャライザ」(コードを中括弧でくくったもの)を置くことができます。
class Hoge(param : String) { val list = ArrayList<String>() { list.add(param) } }
ここで、主要コンストラクタはArrayListを生成し、それを list に割り当て、 param の値をそのリストに追加します。
その場で対応するプロパティを宣言するために、主要コンストラクタのパラメータには接頭辞として val または varキーワードを付けられます。
class Circle(val x : Double, val y : Double, val radius : Double)
クラス本体は任意です。上記の例は、x と y と radius という3つのプロパティを持ち、それらを明白に初期化するための3つのパラメータを含む主要コンストラクタを持ったクラスの完全な宣言です。
そのためKotlinでは、Beanスタイルのデータクラスを宣言するのにたくさんのコードを書かなくて済みます。少なくともJavaよりかは。
クラスメンバ
クラスは次のような種類のメンバを持ちます
- 関数
- プロパティ
- その他のクラス
- Object declaration
継承
Kotlinでは、すべてのクラスがAnyクラスをスーパークラスとして持ちます。これはデフォルト(スーパークラスが宣言されていないとき)のスーパークラスです。Javaで言うObjectクラスのようなものです。
でも Any は java.lang.Object ではありません。Anyはいかなるメンバも持っていません(equals(), hashCode(), toString()でさえも)。任意のオブジェクトでtoString()を呼べない、という意味ではありません。呼び出すことはできますが、それは拡張関数によるものでしょう。(詳細についてはまた今度)
明示的なスーパータイプ(基底型)を宣言するには、クラスヘッダのコロンの後にその型を置きます。次のコードを見てください*1。
open class Base(p : Int) class Derived(p : Int) : Base(p)
ご覧のとおり、基底型はその場で、主要コンストラクタの引数を使って初期化できます(というか、初期化しなければなりません)。
openアノテーションはJavaのfinalとは逆です。つまり、そのクラスを他のクラスで継承することを許可するのです。Kotlinではすべてのクラスがデフォルトでfinalです。これはEffective Javaの項目17「Design and document for inheritance or else prohibit it*2」に対応しています。
メンバのオーバライド
これまで述べてきたとおり、Kotlinは物事を明示的にすることに固執しています。Javaとは異なり、Kotlinではメンバをオーバライドするためには明示的なアノテーションが必須です。openアノテーションとoverrideアノテーションです。次のコードを見てください*3。
open class Base { open fun v() {} fun nv() {} } class Derived() : Base { override fun v() {} }
Derived.v()にはoverrideアノテーションが必須です。もしそれが見つからないと、コンパイラは文句を言ってきます。Base.nv()のように関数にopenアノテーションが付いていない場合、サブクラスで同じシグネチャを持つメソッドは宣言できません。overrideが付いていようが、いまいが。finalクラス(openアノテーションが付いていないクラス)ではopenメンバは宣言できません。
overrideが付いたメンバはオープンです。つまり、サブクラスでオーバライドされるかも知れません。再オーバライドを禁止したい場合はfinalを使います。次のコードを見てください*4。
open class AnotherDerived() : Base { final override fun v() {} }
待った!どうやってライブラリをハックすればいいんだ?!
Kotlinにおけるオーバライディングのアプローチ(デフォルトでクラスやメンバがfinalであること)の問題は、ライブラリ設計者の意図していないオーバライドが困難であることと、そういったオーバライドが汚いハックをもたらしてしまうことです。
これは次の理由で不利ではありません。