ScalaでもリフレクションでtoStringしたい
はじめに
Scalaを始めたのでとりあえずリフレクションでtoStringしてみました。 弊社にとってリフレクションでtoStringするのはもはや挨拶のようなものです。
環境
Scalaのバージョンは2.12.3です。
真のジェネリック
当たり前かどうかは分かりませんが、JVMの型パラメータはコンパイル時に消去されてしまい実行時に取れません。 この辺の問題は『真のジェネリック問題』として2012年をピークに激しく議論されていたようですが、流れ弾はいやなのでとりあえずスルーします。
当然JVMの民*1であるScalaもこの制約を受けます。その為、CLRの民であるVBのように
Sub Hoge(Of T)(obj As T) Dim t As Type = GetType(T) End Sub
のようにGetType(T)
で一発で型情報を取得してアビャーするわけにはいかないようです。
そこで、Scalaはコンテキストバインドという形で型に関する情報をコンパイル時に必要なメソッド等に引き渡すコードを生成し、そこで型情報をやり取りするようです。
仮に
def reflectToString[T: TypeTag : ClassTag](value: T): Unit
のようなシグネチャを持つメソッドは以下のようにコンパイルされます。
> javap -private .\Hello.class Compiled from "Hello.scala" public final class example.Hello { public static <T> void reflectToString(T, scala.reflect.api.TypeTags$TypeTag<T>, scala.reflect.ClassTag<T>); public static void main(java.lang.String[]); }
本来の引数の後ろに型情報に関するパラメータを追加し、コンパイラがコンパイル時に型情報を暗黙的に追加するようです。
package example import scala.reflect._ import scala.reflect.runtime.universe._ class Hoge(val name: String) class Person(val name: String, val age: Int) object Hello { def main(args: Array[String]): Unit = { val p = new Person("Hoge", 10) reflectToString(p) println() val h = new Hoge("HogeHoge") reflectToString(h) } def reflectToString[T: TypeTag : ClassTag](value: T): Unit = { val fs: Iterable[TermSymbol] = typeOf[T] .decls .filter(_.isTerm) .filter(!_.isMethod) .map(_.asTerm) val rm: Mirror = typeTag[T].mirror val im: InstanceMirror = rm.reflect(value) for (it <- fs) { val fm: FieldMirror = im.reflectField(it) println(s"${it.name}= ${fm.get}") } } }
name = Hoge age = 10 name = HogeHoge
TypeTag
とってTermSymbol
とってRuntimeMirror
とってInstanceMirror
とってFieldMirror
とってget
するのは.NETのリフレクションに慣れた身からするとなんか複雑に感じますが、まぁしゃーない。
おわり