前回は、Scalaのリフレクションを使う際の、簡単な導入について書きました。今度は、実際にReflection APIを使用して、Scalaのクラスやメソッドから情報を取得していく方法を書いていきたいと思います。
これまでに書いたエントリのまとめは、こちらです。
導入編
http://d.hatena.ne.jp/Kazuhira/20130730/1375192075
定義情報取得編 - 1
http://d.hatena.ne.jp/Kazuhira/20130801/1375370390
定義情報取得編 - 2
http://d.hatena.ne.jp/Kazuhira/20130803/1375526971
インスタンス操作編
http://d.hatena.ne.jp/Kazuhira/20130804/1375604912
オマケ
http://d.hatena.ne.jp/Kazuhira/20130804/1375607954
以降の記述では、前回紹介したscala-reflect.jarの依存関係の準備と、import文として以下が書かれているものとします。
import scala.reflect.runtime.universe
Typeを取得する
Scalaでの定義情報を取得する際には、まずはTypeを取得します。TypeTagから取得するメソッドといきなりTypeを取得するメソッドがありますが、いずれも最初にimportしているuniverseのメソッドになります。
JavaUniverse
http://www.scala-lang.org/api/current/index.html#scala.reflect.runtime.JavaUniverse
基本的には、以下のようなメソッドを定義してTypeを取得することになります。
def getType[A : universe.TypeTag](clazz: Class[A]): universe.Type =
universe.typeTag[A].tpe
このTypeTagというのはコンパイラが生成するものらしく、これを取得するためにはImplicit ParameterまたはContext Boundを使用するようですが、上記はContext Boundで取得している例になります。UniverseからTypeTagを取得し、そこからTypeを取得しています。
別にClassクラスを引数にしなくても構いません。
def getTypeByInstance[A : universe.TypeTag](target: A): universe.Type =
universe.typeTag[A].tpe
また、Universe#typeOfを使用することで、いきなりTypeを取得することもできます。
def getTypeOtherWay[A : universe.TypeTag](clazz: Class[A]): universe.Type = universe.typeOf[A] def getTypeOtherWayByInstance[A : universe.TypeTag](target: A): universe.Type = universe.typeOf[A]
ここで出てきたUniverse以外のクラスは、以下になりますね。
TypeTag
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.TypeTags$TypeTag
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
以降、ここで定義したメソッドを使用してTypeを取得します。
使用例
取得対象の型。
class SampleClass
使用したコードのサンプル。
val theType = getType(classOf[SampleClass])
先ほど定義したメソッドの戻り値は、すべて同じ結果になります。
require(getType(classOf[SampleClass]) =:= getTypeByInstance(new SampleClass)) require(getTypeOtherWay(classOf[SampleClass]) =:= getTypeOtherWayByInstance(new SampleClass)) require(getType(classOf[SampleClass]) =:= getTypeOtherWay(classOf[SampleClass]))
フィールド(valやvar)を取得したい
Type#declaration、またはType#memberを使用します。
val theType = getType(...) theType.declaration(universe.newTermName("取得したいvalやvarの名前")) theType.member(universe.newTermName("取得したいvalやvarの名前"))
この時、引数はStringではなくNameとなります。Nameは、Universe#newTermNameで作成します。
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
NameApi
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Names$NameApi
declarationとmemberは何が違うかというのかは、また今度説明します。両方共、スコープがprivateとか関係なく取得できます。
これらのメソッドの戻り値の型は、Symbolです。
Symbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol
使用例
取得対象のサンプル。
class SampleClass { val valField: String = "valField" private val privateValField: String = "privateValField" var varField: String = "varField" private var privateVarField: String = "privateVarField" }
使用したコードのサンプル。
val theType = getType(classOf[SampleClass]) require(theType.declaration(universe.newTermName("valField")) != universe.NoSymbol) require(theType.member(universe.newTermName("varField")) != universe.NoSymbol) require(theType.declaration(universe.newTermName("privateVarField")) != universe.NoSymbol) require(theType.member(universe.newTermName("privateValField")) != universe.NoSymbol)
該当する定義が見つからなかった場合は、例外ではなくNoSymbolが返ります。
require(theType.member(universe.newTermName("noSuchField")) == universe.NoSymbol)
なんですが、実はこのvalとvarの取得方法は、若干微妙なところが混じっています。
メソッドを取得したい
実は、フィールドと同じで、declarationまたはmemberを使用します。
使用例
取得対象のサンプル。
class SampleClass { def method(param: String): String = "method" }
使用したコードのサンプル。
val theType = getType(classOf[SampleClass]) require(theType.declaration(universe.newTermName("method")) == theType.member(universe.newTermName("method"))) require(theType.declaration(universe.newTermName("noSuchMethod")) == universe.NoSymbol)
取得できなかった場合に、NoSymbolが返ってくるところも同じです。
コンストラクタを取得したい
フィールド、メソッドと同じくdeclarationまたはmemberを使用します。
ただし、コンストラクタの名前の指定には、Universe#nme#CONSTRUCTORを使用します。
getType(...).declaration(universe.nme.CONSTRUCTOR) getType(...).member(universe.nme.CONSTRUCTOR)
TermNamesApi
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.StandardNames$TermNamesApi
実体は
getType(...).declaration(universe.newTermName("<init>"))
なのですが、ここは定義されている値を使用しましょう。
使用例
取得対象のサンプル。
class SampleClass
使用したコードのサンプル。
val theType = getType(classOf[SampleClass])
require(theType.declaration(universe.nme.CONSTRUCTOR) ==
theType.member(universe.nme.CONSTRUCTOR))
クラスの継承関係が知りたい
Typeのメソッドを使用します。
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
全部取得したい場合は、baseClassesを使用します。
val theType = getType(...)
theType.baseClasses
取得したい親の型が分かっている場合は、baseTypeを使用します。
theType.baseType(getType(親の型).typeSymbol)
baseTypeメソッドの引数はSymbolなので、親のTypeを取得した後にSymbolに変換する必要があります。
TypeSymbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$TypeSymbol
使用例
取得対象のサンプル。
class ClassA trait TraitA trait TraitB extends ClassA with TraitA class ClassB extends TraitB
baseClassesを使用したコードのサンプル。
val theType = getType(classOf[ClassB])
theType.baseClasses.foreach(println)
実行結果。
class ClassB trait TraitB trait TraitA class ClassA class Object class Any
自分自身も入った上で、全部抜けます。
自分自身とかObject、Anyが要らないよという場合は、withFilterとかで省けばいいと思います。
theType.baseClasses.withFilter { s => s.asType.toType != theType && s.asType.toType != getType(classOf[Object]) && s.asType.toType != getType(classOf[Any]) }.foreach(println)
baseTypeを使用したコードのサンプル
val theType = getType(classOf[ClassB])
require(theType.baseType(getType(classOf[TraitA]).typeSymbol) == getType(classOf[TraitA]))
型がトレイトや抽象クラスかどうかが知りたい
ClassSymbolのメソッドを使用します。
ClassSymbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$ClassSymbol
Typeを取得した後に、TypeSymbolに変換してからClassSymbolに変換します。この後、各種isXXXメソッドで情報の確認を行います。
// 抽象クラスかどうか getType(...).typeSymbol.asClass.isAbstractClass // トレイトかどうか getType(...).typeSymbol.asClass.isTrait)
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
TypeSymbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$TypeSymbol
使用例
取得対象のサンプル。
abstract class AbstractClass trait Trait class SampleClass
使用したコードのサンプル。
require(getType(classOf[AbstractClass]).typeSymbol.asClass.isAbstractClass) require(getType(classOf[AbstractClass]).typeSymbol.asClass.isTrait == false) require(getType(classOf[Trait]).typeSymbol.asClass.isTrait) require(getType(classOf[Trait]).typeSymbol.asClass.isAbstractClass) require(getType(classOf[SampleClass]).typeSymbol.asClass.isAbstractClass == false) require(getType(classOf[SampleClass]).typeSymbol.asClass.isTrait == false)
トレイトは、抽象クラスとしても判定されるんですか…。
型(Type)が同じかどうか判定したい
Typeの=:=メソッドを使用します。
getType(...) =:= getType(...)
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
使用例
取得対象のサンプル。
class ClassA class ClassB
使用したコードのサンプル。
require(getType(classOf[ClassA]) =:= getType(classOf[ClassA]))
require(getType(classOf[ClassA]) =:= getType(classOf[ClassB]) == false)
型(Type)の継承関係が知りたい
Typeの<:<メソッドを使用します。
getType(子の型) <:< getType(親の型)
Type
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Types$Type
使用例
取得対象のサンプル。
class SuperClass trait TraitA trait TraitB extends TraitA trait TraitC class SampleClass extends SuperClass with TraitB with TraitC class OtherClass
使用したコードのサンプル。
require(getType(classOf[SampleClass]) <:< getType(classOf[SuperClass]))
require(getType(classOf[SampleClass]) <:< getType(classOf[TraitA]))
require(getType(classOf[SampleClass]) <:< getType(classOf[TraitB]))
require(getType(classOf[SampleClass]) <:< getType(classOf[TraitC]))
require(getType(classOf[TraitB]) <:< getType(classOf[TraitA]))
require(getType(classOf[SampleClass]) <:< getType(classOf[OtherClass]) == false)
取得したSymbolの情報が知りたい
フィールドやメソッドを取得した時に、Symbolの各種メソッドでいろいろと調べることができます。
メソッドなのか、publicスコープなのか、implicitが付いているのか、などなど。
Symbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$Symbol
どんなのがあるかは、ScaladocのTestsを見てください。
また、必要な場合はSymbolのままではなく、TermSymbolに変換する必要があります。
TermSymbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$TermSymbol
TermSymbolへは、Symbol#asTermで変換可能です。
使用例
取得対象のサンプル。
class SampleClass { def method(param: String): String = "Hello World" }
使用したコードのサンプル。
val theType = getType(classOf[SampleClass]) require(theType.member(universe.newTermName("method")).isMethod) require(theType.member(universe.newTermName("method")).isPublic) require(theType.member(universe.newTermName("method")).isType == false)
フィールドを取得しても、SymbolのisValやisVarがtrueにならないんですが?
先のフィールド取得の紹介で、若干微妙…みないなことを言っていましたが、実は先ほどの方法でvalやvarで定義されたフィールドを取得しても、TermSymbol#isValやTermSymbol#isVarを判定しても、trueになりません。
class SampleClass { val valField: String = "valField" private val privateValField: String = "privateValField" var varField: String = "varField" private var privateVarField: String = "privateVarField" def method(param: String): String = "method" }
val theType = getType(classOf[SampleClass]) require(theType.member(universe.newTermName("valField")).asTerm.isVal == false) require(theType.member(universe.newTermName("valField")).asTerm.isVar == false) require(theType.member(universe.newTermName("valField")).isMethod) require(theType.member(universe.newTermName("privateValField")).asTerm.isVal == false) require(theType.member(universe.newTermName("privateValField")).asTerm.isVar == false) require(theType.member(universe.newTermName("privateValField")).isMethod) require(theType.member(universe.newTermName("varField")).asTerm.isVal == false) require(theType.member(universe.newTermName("varField")).asTerm.isVar == false) require(theType.member(universe.newTermName("varField")).isMethod) require(theType.member(universe.newTermName("privateVarField")).asTerm.isVal == false) require(theType.member(universe.newTermName("privateVarField")).asTerm.isVar == false) require(theType.member(universe.newTermName("privateVarField")).isMethod)
しかも、isMethodの判定はtrueになります。
これは、MethodSymbolやTermSymbolApiのgetter、setterの説明を見ると、少しわかる?
MethodSymbol
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$MethodSymbol
TermSymbolApi
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Symbols$TermSymbolApi
valやvarはメソッドとして取得され、裏側にTermとしてvalやvarが隠れているということを言っています。
少し試してみましょう。確認対象。
class SampleClass { val valField: String = "valField" private val privateValField: String = "privateValField" var varField: String = "varField" private var privateVarField: String = "privateVarField" def method(param: String): String = "method" def getField(): String = "field" def setField(field: String): String = "field" }
検証コード。
val theType = getType(classOf[SampleClass]) require(theType.member(universe.newTermName("valField")).asMethod.isGetter) require(theType.member(universe.newTermName("valField")).asMethod.isSetter == false) require(theType.member(universe.newTermName("valField")).asMethod.getter != universe.NoSymbol) require(theType.member(universe.newTermName("valField")).asMethod.setter == universe.NoSymbol) require(theType.member(universe.newTermName("privateValField")).asMethod.isGetter) require(theType.member(universe.newTermName("privateValField")).asMethod.isSetter == false) require(theType.member(universe.newTermName("privateValField")).asMethod.getter != universe.NoSymbol) require(theType.member(universe.newTermName("privateValField")).asMethod.setter == universe.NoSymbol) require(theType.member(universe.newTermName("varField")).asMethod.isGetter) require(theType.member(universe.newTermName("varField")).asMethod.isSetter == false) require(theType.member(universe.newTermName("varField")).asMethod.getter != universe.NoSymbol) require(theType.member(universe.newTermName("varField")).asMethod.setter != universe.NoSymbol) require(theType.member(universe.newTermName("privateVarField")).asMethod.isGetter) require(theType.member(universe.newTermName("privateVarField")).asMethod.isSetter == false) require(theType.member(universe.newTermName("privateVarField")).asMethod.getter != universe.NoSymbol) require(theType.member(universe.newTermName("privateVarField")).asMethod.setter != universe.NoSymbol)
valもvarも、isGetterはすべてtrueが返り、isSetterはfalse…。varもそうなのか…。
Scaladocにある通り、setterやgetterはval、varの時は直感的な値が返ってきているようです。valはsetterのみ、varはgetter、setter両方ですね。
が、getter/setterで取得できるSymbolでも、相変わらずisValやisVarはfalseとなります。
require(theType.member(universe.newTermName("valField")).asMethod.getter.asTerm.isVal == false) require(theType.member(universe.newTermName("privateValField")).asMethod.getter.asTerm.isVar == false) require(theType.member(universe.newTermName("varField")).asMethod.getter.asTerm.isVar == false) require(theType.member(universe.newTermName("varField")).asMethod.setter.asTerm.isVar == false) require(theType.member(universe.newTermName("privateVarField")).asMethod.getter.asTerm.isVar == false) require(theType.member(universe.newTermName("privateVarField")).asMethod.setter.asTerm.isVar == false)
なんか、同じものが返ってきてるっぽいんですよね。
require(theType.member(universe.newTermName("valField")) == theType.member(universe.newTermName("valField")).asMethod.getter)
ちなみに、メソッド名がsetXXX/getXXXだからといって、isGetterなどがtrueを返したりするわけでもありません。
require(theType.member(universe.newTermName("method")).asMethod.getter == universe.NoSymbol) require(theType.member(universe.newTermName("getField")).asMethod.getter == universe.NoSymbol) require(theType.member(universe.newTermName("getField")).asMethod.isGetter == false) require(theType.member(universe.newTermName("setField")).asMethod.setter == universe.NoSymbol) require(theType.member(universe.newTermName("setField")).asMethod.isGetter == false) require(theType.member(universe.newTermName("setField")).asMethod.isSetter == false)
さて、どういう時にisValやisVarはtrueを返すんでしょう。
とりあえず分かっているのは、こういう時ですかね。
val vals = getType(classOf[SampleClass]).declarations.filter { _.asTerm.isVal } println(vals) val vars = getType(classOf[SampleClass]).declarations.filter { _.asTerm.isVar } println(vars)
declarationsやmembersでfilterをかけた時は、判定できます。以下は、printlnの出力結果です。
Scope{ private[this] val valField: <?>; private[this] val privateValField: <?> } Scope{ private[this] var varField: <?>; private[this] var privateVarField: <?> }
これについては、ちょっとうーん?って感じです…。
思ったより書くこといっぱいあったので、また分割しまーす。