Kotlin Advent Calendar 2012 (全部俺)

JavaプログラマのためのKotlin入門

19日目:キーワード object の威力

f:id:ngsw_taro:20051017000521j:plain

今日はキーワード objectの機能について紹介します。この object  というキーワードは3つの機能を提供します。オブジェクト式、オブジェクト宣言、クラスオブジェクトの3つです。これらの機能は名前も使用するキーワードも似ているので、混同しないよう気をつけて学んでいきましょう。

オブジェクト式

匿名クラスというものがあります。既存のクラスのサブクラスを使用箇所で定義し、そのままインスタンス化するという手法です。Kotlin でもできます。Kotlinではインスタンス生成に new を使用しないということを念頭に置くと直感的に次のようなコードを思い浮かべるかと思います。

// Foo はクラス
val foo = Foo() {
}

しかし、この記述では、コンストラクタの最後の引数に関数リテラルを渡しているとコンパイラは解釈します。そこで匿名クラスのためのキーワードが用意されています。それが object です。クラス Foo の匿名サブクラスのインスタンスを返す式を次のように記述することができます。

object : Foo() {
}

サブクラスなのでスーパクラスの関数を(許可されていれば)オーバライドすることはもちろん、新たにメンバを定義することも可能です。また、クラス Any の匿名サブクラスのインスタンスが欲しい場合は、スーパクラスを指定しない次の記述ができます。

object {
}

「オブジェクトを返す」という意味で、この機能をオブジェクト式と言います。

オブジェクト宣言

Kotlinでは言語の機能としてシングルトンをサポートしています。シングルトンとしたいクラスは、キーワード class の代わりに object を使用します。このようにキーワード object によってシングルトンクラスを宣言する機能をオブジェクト宣言と言います。

trait Greeter {
  fun greet()
}

object GreeterImpl : Greeter {
  override fun greet() {
    println("Hello")
  }
}

fun main(args : Array<String>) {
  GreeterImpl.greet() // Hello
  
  val greeter : Greeter = GreeterImpl
  greeter.greet() // Hello
}

オブジェクト宣言したクラスでは、プライマリ・コンストラクタを定義できないことに注意してください。

クラスオブジェクト

Javaでは、クラスは staticメンバ(クラスメンバ)を持つことができます。しかしKotlinには static というキーワードはありません。その代わり、関数や変数をクラスに属すことなく名前空間レベルに置くことができます。ほとんどの場合、これで問題はありませんが、困ることもあります。クラス内部の privateメンバにアクセスする場合です。よくあるのがコンストラクタを private にして、代わりに staticファクトリメソッドを提供するという手法です*1

Javaで言う staticメンバをKotlinではクラスオブジェクトによって実現しています。クラスオブジェクトはクラス内に定義されるので、クラス内のprivateなメンバにアクセスできます。

クラスオブジェクトはキーワード class object によって宣言します。そこから波括弧によってブロックを形成します。その中に、関数や変数を定義していきます。

class Foo private() {
  
  class object {
    fun create() = Foo()
  }
}

上記の例ではクラス Foo の中にクラスオブジェクトとして関数 create を定義しました。クラス Foo のプライマリ・コンストラクタは private として宣言されており、Foo の外側ではこのコンストラクタを呼び出せません。クラス Foo のコンストラクタは隠蔽されており、そのインスタンスはクラスオブジェクトの関数 create により提供される、と言い換えることができます。次に関数 create を呼び出す例を示します。

val foo = Foo.create()

クラス、ドット、クラスオブジェクトといった具合にアクセスすることができます。これはJavaのstaticメンバと同じですね。

クラスオブジェクトは、staticメンバと違いインスタンスメンバとソースコード上、混同しないという可読性の点でのメリットがあります。また、クラスオブジェクトはトレイトをミックスインすることができます。

trait Factory<T> {
  fun create() : T
}

class Foo private() {
  
  class object : Factory<Foo> {
    override fun create() = Foo()
  }
}

fun main(args : Array) {
  val factory : Factory<Foo> = Foo
  val foo = factory.create()
}

まとめと次回予告

今日はキーワード object を用いる3つの機能について学びました。まず1つ目がオブジェクト式でした。これは匿名サブクラスの定義からインスタンス化までを行うための機能です。次にオブジェクト宣言を学びました。これはクラスをシングルトンにする機能です。最後にクラスオブジェクトについて学びました。クラス内に属してインスタンスに属さない関数や変数の定義方法を知ることができました。

そろそろアドベントカレンダーも終わりに近づいています。明日、明後日はまだ見ていないKotlinの言語機能について見て行きましょう。

日記

今週末の飲み会のための店をまだ押さえていない。ま、なんとかなるか。

*1:Effective Java 第2版「項目1 コンストラクタの代わりにstatic ファクトリーメソッドを検討する」を参照してください。