CLOVER🍀

That was when it all began.

ScalaのReflectionについて、まとめてみる - 定義情報取得編 - 1

前回は、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: <?>
}

これについては、ちょっとうーん?って感じです…。

思ったより書くこといっぱいあったので、また分割しまーす。