S-JIS[2010-12-30/2015-09-22] 変更履歴

Scalaリフレクション

Scalaのリフレクション関連のクラスは、scala.reflectパッケージに入っている。


クラスの取得・判定

JavaでClassクラスが取得できるのと同様に、Scalaでも取得できる。

Scala Java相当 備考
val c = classOf[クラス名]
Class c = クラス名.class;
 
val c = obj.getClass()
val c = obj.getClass
Class c = obj.getClass();
AnyRefの派生クラスで使用可能。
objがnullだとNullPointException。
obj.isInstanceOf[クラス名]
obj instanceof クラス名
objがnullでも使用可能。
val o = obj.asInstanceOf[クラス名]
クラス名 o = (クラス名)obj;
objがnullでも使用可能。

ちなみに、isInstanceOfやasInstanceOfが長めでくどいメソッド名になっているのは、「なるべく使うな」という意思表示らしい。

isInstanceOfで判定してからasInstanceOfでキャストするなら、match式の方が便利


Scala用メソッド名の変換

Scalaでは記号入りの名前(メソッド名やフィールド名)を付けることが出来る。[2011-04-16]
その際、コンパイルされたJavaコードでは、Javaで使える文字に変換される。例えば「name_=」は「name_$eq」となる。

Scala記号 Java記号
~ $tilde
= $eq
< $less
> $greater
! $bang
# $hash
% $percent
^ $up
& $amp
| $bar
Scala記号 Java記号
* $times
/ $div
+ $plus
- $minus
: $colon
; $u003B
\ $bslash
? $qmark
@ $at


※ピリオド「.」やカンマ「,」といった文字(文法上使用されているので、通常は識別子として使用しない文字だと思われる)は、Unicodeの文字コード「$u002E」や「$002C」になるようだ。[2015-09-22]


これらの変換を行うのに、NameTransformerオブジェクトが使える。

scala> import scala.reflect.NameTransformer
import scala.reflect.NameTransformer

scala> NameTransformer.encode("name_=")
res1: String = name_$eq

scala> NameTransformer.decode("name_$eq")
res2: String = name_=

参考: kmizuさんのscala_tips


コンパニオンオブジェクトの取得

ケースクラスを作ると、コンパニオンオブジェクトが作られる。[2011-03-09]
「classOf[クラス名]」で取れるのはクラスの方。オブジェクトはコンパイルしてJavaのクラス化すると「$」付きのクラス名になる。

scala> case class C(a:Int, b:String)
defined class C

scala> classOf[C]
res0: java.lang.Class[C] = class C

scala> classOf[C$]
<console>:21: error: not found: type C$
       classOf[C$]
               ^

scala> Class.forName(classOf[C].getName + "$")
res1: java.lang.Class[_] = class C$

オブジェクトはVM内で唯一のインスタンスなので、このClassからインスタンス生成(newInstance())することは出来ない。
と思ったのだが。

  フィールド0個 フィールド1個 フィールド2個
ケースクラス
case class C0
case class C1(a: String)
case class C2(a: Int, b:Int)
オブジェクト
のクラス
scala> val c0 = Class.forName(classOf[C0].getName+"$")
c0: java.lang.Class[_] = class C0$
scala> val c1 = Class.forName(classOf[C1].getName+"$")
c1: java.lang.Class[_] = class C1$
scala> val c2 = Class.forName(classOf[C2].getName+"$")
c2: java.lang.Class[_] = class C2$
インスタンス生成
scala> val n0 = c0.newInstance
n0: Any = <function0>
scala> val n1 = c1.newInstance
n1: Any = <function1>
scala> val n2 = c2.newInstance
n2: Any = <function2>
キャスト
scala> val f0 = n0.asInstanceOf[Function0[C0]]
f0: () => C0 = <function0>
scala> val f1 = n1.asInstanceOf[Function1[String,C1]]
f1: (String) => C1 = <function1>
scala> val f2 = n2.asInstanceOf[Function2[Int,Int,C2]]
f2: (Int, Int) => C2 = <function2>
apply()実行
scala> f0()
res33: C0 = C0()
scala> f1("abc")
res34: C1 = C1(abc)
scala> f2(123, 456)
res35: C2 = C2(123,456)

newInstanceでFunctionが返って来ているので何だろうと思って実行(apply)してみたら、ケースクラスのインスタンスが生成できている。
ということは、オブジェクトのnewInstanceが成功しているってことか(苦笑)

ケースクラスのコンパニオンオブジェクトはFunctionを継承していて、クラス自体はfinalだけれどもコンストラクターは特に定義されていない(つまりデフォルトのコンストラクターとしてpublicで引数なしのコンストラクターが存在する)から、newInstanceが実行できてしまうようだ。

public final class C0$ extends AbstractFunction0 implements ScalaObject, Serializable {	//jad
〜
	public static final C0$ MODULE$ = this;

	static 
	{
		new C0$();
	}
}

で、どうもMODULE$というフィールドでインスタンスを保持しているようで、これがpublicだから、こっちから取る方が良い気がする。

scala> val m1 = c1.getField("MODULE$").get(null).asInstanceOf[Function1[String, C1]]
m1: (String) => C1 = <function1>

scala> m1("abc")
res37: C1 = C1(abc)

そもそもリフレクションでコンパニオンオブジェクトを取得しようとすること自体が良くない気もするけど(爆)


ちなみに、SeqやMap等のコレクション(Traversable)には、コンパニオンオブジェクトを取得するメソッドが用意されている。[2011-10-01]

val seq = Seq[Any]()
val obj = seq.companion

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