S-JIS[2011-01-10/2011-06-25] 変更履歴

Scalaオブジェクト

Scalaのオブジェクトのメモ。


概要

Javaではstatic(静的)なメンバー(フィールド・メソッド)というものを定義できた。
静的なフィールドとは、アプリケーション内(JavaVM内)に一箇所にしか存在しないフィールド(データ)のこと。
(通常の(動的な)フィールドは、インスタンス毎にデータを保持する)

Scalaにはstaticは無い。代わりにシングルトンオブジェクトを作成する機能がある。
シングルトンとは、インスタンスがアプリケーション内に1つしか存在しないこと。
クラスのインスタンスが1つしか作られないとき、その唯一のインスタンスをシングルトンオブジェクトと言う。

※オブジェクトのメンバーはstaticメンバーの様に扱えるが、実際にはstaticではない


object

Scalaでシングルトンオブジェクトを定義するにはobjectを使う。

classと同様に、他クラスを継承したい場合はextends、トレイトを継承したい場合は同様にwithが使える。

インスタンスが自動的に生成される為、コンストラクターの引数を定義することは出来ない。
当然、補助コンストラクターを持つことも出来ない。

アクセス修飾子object オブジェクト名 〔extends クラス〔with 他トレイト〕…〕{
  メンバー定義
}

メンバーにはclassと同様に変数定数メソッド型の別名等を定義できる。
ただし(シングルトンオブジェクトがさらに他クラス等に継承されることは無いので)抽象メンバーは定義できない。
メンバー定義が不要な場合はとげ括弧を省略できる。

※「シングルトン」と言っても、特定のスコープ内で唯一となるだけである(特に他クラスの内部オブジェクトとなる時)


mainメソッド

プログラムを実行する際は、オブジェクト内のmainメソッド(引数がString配列で戻り型がUnit)から実行される。

object オブジェクト名 {

  def main(args: Array[String]) : Unit = {
    〜
  }
}

戻り型がUnitの場合は「: Unit =」が省略できるので、下記のようにも書ける。

  def main(args: Array[String]) {
    〜
  }

実際には、戻り型がUnitでなくても実行できはするようだが…。[2011-01-18]

プログラムの実行例


コンパニオンオブジェクト

オブジェクトは、クラス名と同じ名前を付けることが出来る。
そういうオブジェクトをコンパニオンオブジェクト(companion object)と呼ぶ。

class Sample {
}

object Sample {
}

コンパニオンオブジェクトは、クラスの共通の操作・処理を扱う。
(同一ソース・同一パッケージにクラスとオブジェクトを記述すれば、オブジェクトからクラスにアクセスできる)

なお、コンパイルすると、クラスの方はクラス名のファイルが出来るが、オブジェクトの方は$が付いたファイルも作られる。
$の付いていない方が入り口で、$の付いている方に実際の処理が入っている。

  ソース上の記述 生成されるファイル
クラス class Sample Sample.class
オブジェクト object Sample Sample.class
Sample$.class

コンパニオンオブジェクトをリフレクションで取得する方法


ファクトリー

コンパニオンオブジェクトで、クラスのインスタンスを生成する。いわばファクトリーの役目を負う。

Scala Java相当 備考
class Sample private(a:Int) {
  protected var n = a 


}

object Sample {
  def apply(a:Int) = {
    new Sample(a)
  }
}
Sample.apply(123)
public class Sample {
  protected int n;
  private Sample(int a) {
    this.n = a;
  }


  public static Sample apply(int a) {
    return new Sample(a);
  }
}
Sample.apply(123)
オブジェクト内でapply()メソッドを定義し、
インスタンスを生成したい場合にそれを呼び出すようにする。
Sample(123)
  メソッド名がapplyの場合は呼び出し時にメソッド名を省略できるので、
あたかもクラス名を指定してそのインスタンスを生成しているかのように見える。

シングルトンオブジェクトを使ったリテラル生成(ファクトリーパターン)


パターンマッチ

match式のcaseにマッチさせる為には、コンパニオンオブジェクトでunapply()またはunapplySeq()メソッドを定義する。

class Sample (
  val n1:Int,
  val n2:Int
)

object Sample {
  def unapply(s: Sample) = {
    Some((s.n1, s.n2))
  }
}
scala> val s = new Sample(11, 22)
s: Sample = Sample@f1e2d9

scala> s match {
     |   case Sample(a, b) => a + b
     | }
res2: Int = 33

インポートの注意

同名のクラスとオブジェクトを定義した場合、その名前をインポートするとオブジェクトの方が優先される
ようだが、コンパイル時に以下の警告が出る。

import companion.Sample

object Companion {
  def main(args: Array[String]) {
    val s1 = Sample(123)     //オブジェクトのapply()メソッド
    val s2 = new Sample(123) //クラスのコンストラクター
  }
}
> fsc companion.scala
C:\temp\scala\companion.scala:1: warning: imported `Sample' is permanently hidden by definition of object Sample
import companion.Sample
       ^

そして、クラスの方にはアクセスできない。

C:\temp\scala\companion.scala:6: error: constructor Sample cannot be accessed in object Companion
    val s2 = new Sample(123)
                 ^

ケースクラスで作った場合は、このような警告もエラーも発生しない。


ケースオブジェクト

ケースクラスと同様に、オブジェクトでも「case」を付けて定義するとメソッドが自動生成される。

case object オブジェクト名 {
}

ただしオブジェクトはコンストラクターに引数を取れないので、フィールドは作られない。
シングルトンなので、copy()メソッドも作られない。
equals()・canEqual()で等しくなるのは自分自身だけ。
オブジェクトなので、コンパニオンオブジェクトやそれに伴うapply()・unapply()も作られない。

というわけで、せいぜいtoString()が実装される点くらいしか利点が無いような気が^^;


パッケージオブジェクト

Scala2.8かららしいが、パッケージ用のオブジェクトが作れる。[2011-01-15]

package sample

package object pack1 {
  def method1() = println("pack1 method")
}

このようにすると、同名のパッケージの下にあるクラス(オブジェクト・トレイト)から、パッケージオブジェクトの中にあるメソッドが呼べる。

package sample.pack1

object Main1 {
  def main(args:Array[String]) {
    method1()
  }
}

別パッケージからも、パッケージオブジェクトのメソッドをインポートすれば呼ぶことが出来る。

package sample.pack2

import sample.pack1._

object Main2 {
  def main(args:Array[String]) {
    method1()
  }
}

ちなみに、パッケージオブジェクトをコンパイルするとpackage.class・package$.classというクラスファイルが作られる。
つまりパッケージ名が「sample.pack1」でクラス名が「package」「package$」となる。
(Javaでも、パッケージ用のJavadocとアノテーションの為にpackage-info.javaというソースファイルがあったなぁ)


「静的」ではなく「シングルトン」であるということ

Scalaのオブジェクトのメンバーは、Javaのstaticメンバーの様に扱えるように見える。[2011-01-18]
つまり「newでインスタンスを生成してそのインスタンスのメソッドを呼ぶ」のではなく、「クラス名(オブジェクト)を指定して、そのメソッドを呼ぶ」ようなコーディングになる。

しかしシングルトンオブジェクトはあくまで(唯一の)オブジェクトなのであって、そのメンバー(フィールド・メソッド)は静的なメンバーではない

Scala Java類似 Java相当(実際のイメージ) 備考
object O {
  var v = 1
}




var s = O.v
O.v += 1
public class O {
  public static int v = 1;
}




int s = O.v;
O.v++;
public class O$ {
  public int v = 1;

  public static final O$ INSTANCE = new O$();
}

final O$ O = O$.INSTANCE;
int s = O.v;
O.v++;
Javaでの「クラスOに対するstaticフィールドへのアクセス」と
「クラスO$の唯一のインスタンスOに対するフィールドへのアクセス」が
意味合い(使用目的)としては同じになる。
class A {
  object B {
    var v = 1
  }

}
val a1 = new A
val a2 = new A
a1.B.v += 1
↓
a1.B.vは2になるが
a2.B.vは1のまま
a1.Bとa2.Bは別インスタンス
public class A {
  public static class B {
    public static int v = 1;
  }

}
A a1 = new A();
A a2 = new A();
a1.B.v++;
↓
a1.B.vは当然2になり
a2.B.vも2になる
a1.Bとa2.Bは同じもの
public class A {
  public static class B$ {
    public int v = 1;
  }
  public final B$ B = new B$();
}
A a1 = new A();
A a2 = new A();
a1.B.v++;
↓
a1.B.vは2になるが
a2.B.vは1のまま
当然、a1.Bとa2.Bは別インスタンス
「Scalaのオブジェクトのメンバーが
 Javaのstaticメンバーに相当する」
と思っていると、左側のJavaの例の様に思えるかもしれないが、
実際は右側の例の様に、クラスA(のインスタンス)内で唯一のインスタンスが作られ、そのメンバーにアクセスしている。
(そのメンバー自身はstaticではない)
したがって、外側クラスのインスタンスが作られる度に新しい(別個の)インスタンスが作られることになる。

つまりobjectはそのスコープ内で唯一のインスタンスなだけで、アプリケーション(VM)全体で常に“シングルトン(唯一)”であるわけではない。


インナークラスの実体

逆に、classobjectの中でclassを定義すると、コンパイルされ たらどういう扱いになるか。[2011-06-25]

Scala コンパイルされたイメージ(Java) 備考
class C {
  class CC
}
public class C implements ScalaObject {
  public class CC implements ScalaObject {
    public final C $outer = C.this;
  }
}
classの中のclassは、Javaの通常のインナークラス。
外側クラスのインスタンスも自前で保持するようになっている。
object O {
  class OC
}
public final class O {
  public static class OC implements ScalaObject {}
}
objectの中のclassは、staticクラス。

Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま