Javaを含む多くのオブジェクト指向言語において、クラスはデータ(フィールドやプロパティ)と操作(メソッド)の集合体として定義されます。しかし、実際のプログラミングにおいては、オプションを指定する専用のクラスなど、データのみを保持するクラスもしばしば登場します。
Kotlinではその名の通り、「データクラス」という概念があり、データのみを持つクラスを簡潔に定義できます。データクラスはリスト7のようにclassキーワードの前にdataキーワードを付けて定義します。また、プライマリコンストラクタのみで定義が完成するため、クラス本体を{}で記述する必要はありません
//データクラス。プライマリコンストラクタ後に{}がないことに注目 data class Person3(val firstName: String, val lastName: String = "") …… //データクラスを定義 val person5 = Person3("Tsuyoshi", "Doi")
データクラスはプライマリコンストラクタのみで完結しており、保持するデータをプロパティとして定義します。データクラスを定義すると、幾つかの便利なメソッドが自動的に定義されます(リスト8)。
//データクラスには自動でtoStringメソッドが定義される println(person5) //結果: 「Person3(firstName=Tsuyoshi, lastName=Doi)」
まずは文字列表現で、「クラス名(プロパティ名=プロパティ値、プロパティ名=……)」のような形式の文字列を返すtoString()メソッドが自動的に定義されます*2。
*2)これまで触れてきませんでしたが、Kotlinにおいてもオブジェクトの文字列表現を取得する際にはJavaと同じくtoString()メソッドが使用されます。
また、データクラスには、インスタンスをコピーするcopy()メソッドも定義されています。copy()メソッドを使えば、同じプロパティ値を持つインスタンスを簡単に生成できます。リスト9はcopy()メソッドを使ってインスタンスのコピーを作り、その一部のプロパティだけを書き換えるサンプルです。
//データクラスはインスタンスをコピーするcopy()メソッドを持つ val person6 = person5.copy() person6.firstName = "TsuyoTsuyo" println(person6) //結果: 「PersonByDataClass(firstName=TsuyoTsuyo, lastName=Doi)」
またデータクラスには、宣言された順番にプロパティを返すcomponentNメソッド(Nには定義順の番号が入る)も定義されています(リスト10)。
//データクラスはN番目に定義されたプロパティを返すcomponentNメソッドを持つ println(person6.component2()) //結果:「Doi」←2番目のプロパティであるlastNameの値
データクラスはそれほど特別な機能ではありませんが、不要なものが省かれるので、クラス定義がより簡潔で分かりやすいものになりますね。
連載第3回でサンプルとして挙げた「あるクラスのインスタンスが常に1つであることを保証する」デザインパターンであるSingletonパターンですが、Kotlinでは「object」キーワードを使うことで簡潔に定義可能です。
//Java版Singletonパターン(マルチスレッドバグ無し版) public class Singleton{ //マルチスレッドバグ無し版ではフィールド宣言でいきなりnewする private static Singleton instance = new Singleton(); public Singleton getInstance(){ //唯一のインスタンスを取得するメソッド return instance; //インスタンスを返す } private Singleton(){} //外部からインスタンスを作れないようにする } ↓ //KotlinでのSingletonパターン object SingletonSample{}
第3回のJavaでのSingletonパターン例はマルチスレッド時バグありでしたので、今回はマルチスレッド対応版で書いてみました。こちらはnullチェックが存在しないので第3回より簡潔になっていますが、Kotlinだとobjectキーワードを使うことで、何と1行で済んでしまいます。
これだけだと分かりづらいので、リスト12で使い方も見ておきましょう。
//設定ファイル読み書きオブジェクト。常に実体は1つ object Config{ //設定ファイル読み書きダミー実装用Map val map = mutableMapOf<String, String>() fun read(key: String): String{ return map[key] ?: "" } fun write(key: String, value: String){ map[key] = value } } …… //シングルトンオブジェクトの使用例 //Configはクラスではなくオブジェクトなので、そのままメソッドを呼び出せる val value = Config.read("test1") //設定読み込み Config.write("test2", "value2") //設定書き込み
ここでは、設定ファイルの読み書きを行うConfigオブジェクトをシングルトンとして定義しています。設定ファイルは1つで、複数のConfigインスタンスから読み書きさせるのは危険なため、Singletonパターンを使用することを想定しています(ただしここではダミー実装として書き換え可能なMapを使って実装しています)。
また、シングルトンオブジェクトとして定義したConfigオブジェクトから「Config.read()」のように、直接メソッドを呼び出しています。
実はKotlinのシングルトンオブジェクトはクラスではなくオブジェクトそのものです。クラスをインスタンス化することでオブジェクトを作成するJavaとは異なり、Kotlinにおいてはobjectキーワードを使うことで、その場でオブジェクトを宣言できます(その名の通り「オブジェクト宣言」と呼ばれます)。シングルトンオブジェクトにおいては、そもそもクラス自体が存在しないため、常に単一のインスタンスであることが保証されます。
先ほど、「Kotlinにはstaticメソッド(クラスメソッド)がない」と記しましたが、Kotlinにはクラスのstatic変数(クラス変数)も同じく存在しません。staticメソッドはクラス外の関数である程度代替できますが、static変数については、直接の代替はありません。
Kotlinには、クラス内で「companion」キーワードを使って「コンパニオンオブジェクト」と呼ばれるオブジェクトを宣言でき、staticメソッド、static変数の代替手段として用いることができます。リスト13はコンパニオンオブジェクトを使ってメソッドと変数を定義する例です。
//コンパニオンオブジェクトの使用例 class CompanionSample(val firstName: String, val lastName: String = ""){ //companionキーワードを使ってコンパニオンオブジェクトを宣言 companion object{ //helloメソッドを定義 fun hello(name: String){ println("こんにちは $name さん") } //プロパティを定義 val PI = 3.141592653589793 } } …… //コンパニオンオブジェクトで定義されたメソッドをstaticメソッドのように呼び出し CompanionSample.hello("土井") //結果: 「こんにちは 土井 さん」 //コンパニオンオブジェクトで定義されたプロパティをstatic変数のように呼び出し println(CompanionSample.PI) //結果: 「3.141592653589793」
コンパニオンオブジェクト内で定義されたメソッドやプロパティは、「クラス名.メソッド名」や「クラス名.プロパティ名」といった形式で呼び出すことができ、Javaのstaticメソッド、static変数に近い形で使用できます。これも先ほどのオブジェクト宣言の応用で、クラス定義の中に、そのままオブジェクトを宣言していることになります。
今回はKotlinのクラスに関連した機能を解説しました。プロパティやデフォルト引数などはJavaにはなかった機能ではあるものの、比較的なじみやすい、すぐに使えそうな機能ですね。一方、プライマリコンストラクタでのプロパティ定義やデータクラスなどは、ちょっと毛色が異なると感じた方も多いことでしょう。しかし、これらの機能は強力で、冗長なコードを省き、より簡潔で直感的な実装を可能にしてくれます。
また、オブジェクト宣言については、直接Javaに対応する機能がないため、やや戸惑うかもしれません。まずは今回紹介したようなシングルトンオブジェクトやstatic変数、メソッドの代替といった一般的なユースケースで使い方をつかむようにしましょう。
次回は、クラス関連の続きとして、継承や可変長引数、拡張関数などの機能について解説します。
Copyright © ITmedia, Inc. All Rights Reserved.