S-JIS[2011-01-10/2011-06-25] 変更履歴
Scalaのオブジェクトのメモ。
|
Javaではstatic(静的)なメンバー(フィールド・メソッド)というものを定義できた。
静的なフィールドとは、アプリケーション内(JavaVM内)に一箇所にしか存在しないフィールド(データ)のこと。
(通常の(動的な)フィールドは、インスタンス毎にデータを保持する)
Scalaにはstaticは無い。代わりにシングルトンオブジェクトを作成する機能がある。
シングルトンとは、インスタンスがアプリケーション内に1つしか存在しないこと。
クラスのインスタンスが1つしか作られないとき、その唯一のインスタンスをシングルトンオブジェクトと言う。
※オブジェクトのメンバーはstaticメンバーの様に扱えるが、実際にはstaticではない
Scalaでシングルトンオブジェクトを定義するにはobjectを使う。
classと同様に、他クラスを継承したい場合はextends、トレイトを継承したい場合は同様にwithが使える。
インスタンスが自動的に生成される為、コンストラクターの引数を定義することは出来ない。
当然、補助コンストラクターを持つことも出来ない。
〔アクセス修飾子〕 object オブジェクト名 〔extends クラス〔with 他トレイト〕…〕{ メンバー定義 }
メンバーにはclassと同様に変数・定数・メソッド・型の別名等を定義できる。
ただし(シングルトンオブジェクトがさらに他クラス等に継承されることは無いので)抽象メンバーは定義できない。
メンバー定義が不要な場合はとげ括弧を省略できる。
※「シングルトン」と言っても、特定のスコープ内で唯一となるだけである(特に他クラスの内部オブジェクトとなる時)
プログラムを実行する際は、オブジェクト内のmainメソッド(引数がString配列で戻り型がUnit)から実行される。
object オブジェクト名 { def main(args: Array[String]) : Unit = { 〜 } }
戻り型がUnitの場合は「: Unit =
」が省略できるので、下記のようにも書ける。
def main(args: Array[String]) { 〜 }
実際には、戻り型がUnitでなくても実行できはするようだが…。[2011-01-18]
オブジェクトは、クラス名と同じ名前を付けることが出来る。
そういうオブジェクトをコンパニオンオブジェクト(companion object)と呼ぶ。
class Example { } object Example { }
コンパニオンオブジェクトは、クラスの共通の操作・処理を扱う。
(同一ソース・同一パッケージにクラスとオブジェクトを記述すれば、オブジェクトからクラスにアクセスできる)
なお、コンパイルすると、クラスの方はクラス名のファイルが出来るが、オブジェクトの方は$が付いたファイルも作られる。
$の付いていない方が入り口で、$の付いている方に実際の処理が入っている。
ソース上の記述 | 生成されるファイル | |
---|---|---|
クラス | class Example |
Example.class |
オブジェクト | object Example |
Example.class |
コンパニオンオブジェクトで、クラスのインスタンスを生成する。いわばファクトリーの役目を負う。
Scala | Java相当 | 備考 |
---|---|---|
class Example private(a:Int) { protected var n = a } object Example { def apply(a:Int) = { new Example(a) } } Example.apply(123) |
public class Example { protected int n; private Example(int a) { this.n = a; } public static Example apply(int a) { return new Example(a); } } Example.apply(123) |
オブジェクト内でapply()メソッドを定義し、 インスタンスを生成したい場合にそれを呼び出すようにする。 |
Example(123) |
メソッド名がapplyの場合は呼び出し時にメソッド名を省略できるので、 あたかもクラス名を指定してそのインスタンスを生成しているかのように見える。 |
→シングルトンオブジェクトを使ったリテラル生成(ファクトリーパターン)
match式のcaseにマッチさせる為には、コンパニオンオブジェクトでunapply()またはunapplySeq()メソッドを定義する。
class Example ( val n1:Int, val n2:Int ) object Example { def unapply(s: Example) = { Some((s.n1, s.n2)) } }
scala> val s = new Example(11, 22) s: Example = Example@f1e2d9 scala> s match { | case Example(a, b) => a + b | } res2: Int = 33
同名のクラスとオブジェクトを定義した場合、その名前をインポートするとオブジェクトの方が優先される
ようだが、コンパイル時に以下の警告が出る。
import companion.Example object Companion { def main(args: Array[String]) { val s1 = Example(123) //オブジェクトのapply()メソッド val s2 = new Example(123) //クラスのコンストラクター } }
> fsc companion.scala
C:\temp\scala\companion.scala:1: warning: imported `Example' is permanently hidden by definition of object Example
import companion.Example
^
そして、クラスの方にはアクセスできない。
C:\temp\scala\companion.scala:6: error: constructor Example cannot be accessed in object Companion
val s2 = new Example(123)
^
ケースクラスで作った場合は、このような警告もエラーも発生しない。
ケースクラスと同様に、オブジェクトでも「case」を付けて定義するとメソッドが自動生成される。
case object オブジェクト名 { }
ただしオブジェクトはコンストラクターに引数を取れないので、フィールドは作られない。
シングルトンなので、copy()メソッドも作られない。
equals()・canEqual()で等しくなるのは自分自身だけ。
オブジェクトなので、コンパニオンオブジェクトやそれに伴うapply()・unapply()も作られない。
というわけで、せいぜいtoString()が実装される点くらいしか利点が無いような気が^^;
Scala2.8かららしいが、パッケージ用のオブジェクトが作れる。[2011-01-15]
package example package object pack1 { def method1() = println("pack1 method") }
このようにすると、同名のパッケージの下にあるクラス(オブジェクト・トレイト)から、パッケージオブジェクトの中にあるメソッドが呼べる。
package example.pack1 object Main1 { def main(args:Array[String]) { method1() } }
別パッケージからも、パッケージオブジェクトのメソッドをインポートすれば呼ぶことが出来る。
package example.pack2 import example.pack1._ object Main2 { def main(args:Array[String]) { method1() } }
ちなみに、パッケージオブジェクトをコンパイルするとpackage.class・package$.classというクラスファイルが作られる。
つまりパッケージ名が「example.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)全体で常に“シングルトン(唯一)”であるわけではない。
逆に、classやobjectの中で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クラス。 |