Jea*_*let 8 reflection scala case-class
在Scala中,假设我有一个这样的案例类:
case class Sample(myInt: Int, myString: String)
Run Code Online (Sandbox Code Playgroud)
有没有办法让我获得一个Seq[(String, Class[_])]或更好的Seq[(String, Manifest)]描述案例类的参数?
我正在回答我自己的问题,提供基本解决方案,但我也在寻找替代方案和改进方案.
一个选项,也与Java兼容,不限于案例类,是使用ParaNamer.在Scala中,另一个选项是解析ScalaSig附加到生成的类文件的字节.这两种解决方案都不适用于REPL.
这是我尝试从ScalaSig(使用scalap和Scala 2.8.1)中提取字段的名称:
def valNames[C: ClassManifest]: Seq[(String, Class[_])] = {
val cls = classManifest[C].erasure
val ctors = cls.getConstructors
assert(ctors.size == 1, "Class " + cls.getName + " should have only one constructor")
val sig = ScalaSigParser.parse(cls).getOrElse(error("No ScalaSig for class " + cls.getName + ", make sure it is a top-level case class"))
val classSymbol = sig.parseEntry(0).asInstanceOf[ClassSymbol]
assert(classSymbol.isCase, "Class " + cls.getName + " is not a case class")
val tableSize = sig.table.size
val ctorIndex = (1 until tableSize).find { i =>
sig.parseEntry(i) match {
case m @ MethodSymbol(SymbolInfo("<init>", owner, _, _, _, _), _) => owner match {
case sym: SymbolInfoSymbol if sym.index == 0 => true
case _ => false
}
case _ => false
}
}.getOrElse(error("Cannot find constructor entry in ScalaSig for class " + cls.getName))
val paramsListBuilder = List.newBuilder[String]
for (i <- (ctorIndex + 1) until tableSize) {
sig.parseEntry(i) match {
case MethodSymbol(SymbolInfo(name, owner, _, _, _, _), _) => owner match {
case sym: SymbolInfoSymbol if sym.index == ctorIndex => paramsListBuilder += name
case _ =>
}
case _ =>
}
}
paramsListBuilder.result zip ctors(0).getParameterTypes
}
Run Code Online (Sandbox Code Playgroud)
免责声明:我不太了解ScalaSig的结构,这应该被视为一种启发式方法.特别是,此代码做出以下假设:
ClassSymbol.MethodEntry具有<init>其所有者具有id 0的名称的构造函数.它将ScalaSig在嵌套的case类中失败(因为没有).
此方法也只返回Class实例而不是Manifests.
请随时提出改进建议!
又是我(两年后)。这是使用Scala反射的不同解决方案。它的灵感来自一篇博客文章,该文章本身又受到Stack Overflow 交流的启发。下面的解决方案专门针对上述原始发帖者的问题。
在一个编译单元(REPL:paste或已编译的 JAR)中,包含scala-reflect作为依赖项并编译以下内容(在 Scala 2.11 中测试,可能在 Scala 2.10 中工作):
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object CaseClassFieldsExtractor {
implicit def makeExtractor[T]: CaseClassFieldsExtractor[T] =
macro makeExtractorImpl[T]
def makeExtractorImpl[T: c.WeakTypeTag](c: Context):
c.Expr[CaseClassFieldsExtractor[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if (m.isPrimaryConstructor) => m
}.get.paramLists.head
val extractParams = fields.map { field =>
val name = field.asTerm.name
val fieldName = name.decodedName.toString
val NullaryMethodType(fieldType) = tpe.decl(name).typeSignature
q"$fieldName -> ${fieldType.toString}"
}
c.Expr[CaseClassFieldsExtractor[T]](q"""
new CaseClassFieldsExtractor[$tpe] {
def get = Map(..$extractParams)
}
""")
}
}
trait CaseClassFieldsExtractor[T] {
def get: Map[String, String]
}
def caseClassFields[T : CaseClassFieldsExtractor] =
implicitly[CaseClassFieldsExtractor[T]].get
Run Code Online (Sandbox Code Playgroud)
在另一个编译单元(REPL 中的下一行或使用前一行作为依赖项编译的代码)中,像这样使用它:
scala> case class Something(x: Int, y: Double, z: String)
defined class Something
scala> caseClassFields[Something]
res0: Map[String,String] = Map(x -> Int, y -> Double, z -> String)
Run Code Online (Sandbox Code Playgroud)
这似乎有些过分了,但我无法把它弄得更短。它的作用如下:
caseClassFields中间体。CaseClassFieldsExtractorCaseClassFieldsExtractor是一个具有伴随对象的特征,该伴随对象使用宏定义了该特征的匿名具体子类。该宏可以检查案例类的字段,因为它具有有关案例类的丰富的编译器级信息。CaseClassFieldsExtractor伴随对象必须在检查案例类的前一个编译单元中声明,以便宏在您想要使用它时存在。WeakTypeTag. 这评估为具有大量模式匹配的 Scala 结构,但我找不到任何文档。CaseClassFieldsExtractor.caseClassFields) 中,而不会在尚未定义时过早调用。欢迎任何可以完善此解决方案或解释“隐式”如何准确执行其操作(或者是否可以将其删除)的评论。
| 归档时间: |
|
| 查看次数: |
1900 次 |
| 最近记录: |