通常のクラスではインスタンスを複数生成できるのが当たり前です。これは多くの場合非常に便利なものですが、時に複数あってはならないものも存在します。
シングルトンとは「インスタンスが1つしか無いことを保証する」クラスです。Kotlinではオブジェクト宣言と呼ばれる手法でシングルトンを定義します。
今回はオブジェクト宣言の定義方法とそのルールについて、一緒に確認していきましょう。
オブジェクト宣言の定義方法
オブジェクト宣言の構文はシンプルです。クラス名の前にclassではなく「object」と付け加えるだけ。
object Degawa{
val age = 56
var office = "マセキ芸能社" //varプロパティも設定可能
fun speakEnglish() = "ドゥーユーノーガーガーチキン?"
}
これでインスタンスを1つしか持たない、シングルトンオブジェクトが定義できました。「Degawa」はクラス名でもあり、ただ1つのインスタンス名でもあります。
main関数内で使用してみましょう。
fun main(){
println(Degawa.speakEnglish())
}
//ドゥーユーノーガーガーチキン?
このインスタンスは複数生成されることはありません。プログラム内で最初にアクセスされるときに生成され、プログラムが実行されている間は、同じものが存在し続けます。
fun main() {
val a = Degawa
val b = Degawa
println(a.office) //マセキ芸能社
a.office = "松竹芸能"
println(b.office) //松竹芸能
}
変数aとbはあくまで同じものです。そのため片方のvarプロパティを変更すると、もう片方のプロパティも変更されています。
イメージで見るクラスとの違い
通常のクラスはあくまで設計図でしかなく、インスタンスを構築するためのコンストラクタを持ちます。クラスはコンストラクタを呼び出すことで、初めてインスタンスが生成されます。
しかしオブジェクト宣言は、インスタンス化のための材料は全て揃えた状態で待機しています。そのためコンストラクタは不要であり、型へのアクセスによって実体が生成されます。
例えるならオブジェクト宣言の定義部分が家、main関数が出張先のようなものです。
初期化ブロックを設定する
上記のようにオブジェクト宣言ではコンストラクタ引数が(セカンダリも含め)不要でなければならず、設定できません。しかし初期化ブロックを置くことは可能です。
object Degawa{
init{
println("ヤバイよヤバイよ")
}
}
fun main() {
Degawa
}
//ヤバイよヤバイよ
「Degawa」と書いた時点でシングルトンへのアクセスが発生し、インスタンスが生成されているのが分かります。これを利用してシングルトンのプロパティを初期化することもできます。
//オブジェクト宣言
object Degawa{
val age: Int
init{
print("Degawaの年齢を入力:")
//ユーザーからの入力を元に、ageプロパティを初期化
age = requireNotNull(readLine()).toInt()
}
}
fun main() {
Degawa
println(Degawa.age)
}
//実行結果
Degawaの年齢を入力:56 //56はユーザーによる入力
56
ルール
ここでオブジェクト宣言とシングルトンのルールを確認しておきましょう。
オブジェクト宣言は変数に代入できない
シングルトンのインスタンスは当然変数に代入することができます。しかしオブジェクト宣言自体は代入不可です。
fun main() {
val a = Degawa //オブジェクト宣言が存在すればOK
val b = object Degawa{ //この記述はNG
val age = 56
}
}
//An object expression cannot bind a name
単純に無名クラスを変数に代入するにはオブジェクト式を使いましょう。
fun main() {
val a = object {val x = 1
val y = 2
fun z() = "some"}
}
継承に関するルール
オブジェクト宣言では他のクラスやインターフェイスを継承することができます。
ただしオブジェクト宣言自体にはコンストラクタ引数を設定できないため、スーパークラスにコンストラクタ引数が設定されている場合は、宣言の時点で引数を渡しておかなければならないことに注意してください。
//スーパークラスにコンストラクタ引数が存在
open class Entertainer(val name: String){
val job = "芸人"
}
//インターフェイスは通常通り実装可能
interface SpeakEnglish{
fun speakEnglish(): String
}
//ここでしかスーパークラスのコンストラクタ引数を渡せない
object Degawa: Entertainer("出川哲朗"), SpeakEnglish{
val age = 56
var office = "マセキ芸能社"
override fun speakEnglish() = "ドゥーユーノーガーガーチキン?"
}
fun main(){
println(Degawa.job) //芸人
println(Degawa.speakEnglish()) //ドゥーユーノーガーガーチキン?
}
この逆は不可です。オブジェクト宣言を他のクラスに継承させることはできません。
ネスト
オブジェクト宣言はクラスやオブジェクト宣言の中に含めることができます。いわゆるネスト(入れ子)の状態です。
object Degawa{
object Uchimura{ //DegawaにUchimuraをネスト
val age = 55
val job = "司会"
}
}
ネストされたオブジェクトを呼び出すには外側のシングルトン.内側のシングルトン.メンバ
とします。
fun main(){
println(Degawa.Uchimura.job) //司会
}
まとめ
今回のまとめです。
object オブジェクト名
とすることで、シングルトンを定義できる- シングルトンのインスタンスは1つに限定される
- オブジェクト宣言はコンストラクタを持たないが、初期化ブロックは設定できる
- オブジェクト宣言は他のクラスやインターフェイスを継承できる
- オブジェクト宣言は他のクラスのスーパークラスにはなれない