9

在 Scala 中,假设我有一个这样的案例类:

case class Sample(myInt: Int, myString: String)

有没有办法让我获得一个Seq[(String, Class[_])],或者更好Seq[(String, Manifest)]的,描述案例类的参数?

4

3 回答 3

7

我正在回答我自己的问题以提供基本解决方案,但我也在寻找替代方案和改进方案。


一种与 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
}

免责声明:我不太了解 ScalaSig 的结构,这应该被视为一种启发式方法。特别是,这段代码做了以下假设:

  • 案例类只有一个构造函数。
  • 零位置处的签名条目始终是 a ClassSymbol
  • 该类的相关构造函数是第一个MethodEntry具有 name<init>其所有者具有 id 0 的构造函数。
  • 参数名称将构造函数条目作为所有者,并且始终位于该条目之后。

ScalaSig它会在嵌套案例类上失败(因为 no )。

此方法也只返回Class实例而不返回Manifests。

请随时提出改进建议!

于 2011-06-08T17:02:33.300 回答
3

这是使用纯 Java 反射的不同解决方案。

case class Test(unknown1: String, unknown2: Int)
val test = Test("one", 2)

val names = test.getClass.getDeclaredFields.map(_.getName)
// In this example, returns Array(unknown1, unknown2).

要获得Seq[(String, Class[_])],您可以这样做:

val typeMap = test.getClass.getDeclaredMethods.map({
                x => (x.getName, x.getReturnType)
              }).toMap[String, Class[_]]
val pairs = names.map(x => (x, typeMap(x)))
// In this example, returns Array((unknown1,class java.lang.String), (two,int))

我不确定如何获得Manifests.

于 2013-08-26T05:33:43.063 回答
2

又是我(两年后)。这是使用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

在另一个编译单元中(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)

这似乎有点矫枉过正,但我​​无法让它更短。这是它的作用:

  1. caseClassFields函数创建了一个CaseClassFieldsExtractor隐含地出现、报告其发现并消失的中间体。
  2. CaseClassFieldsExtractor是一个带有伴随对象的特征,它使用宏定义了该特征的匿名具体子类。它是可以检查案例类字段的宏,因为它具有关于案例类的丰富的编译器级信息。
  3. 和它的CaseClassFieldsExtractor伴生对象必须在前一个编译单元中声明为检查案例类的编译单元,以便宏在您想要使用它时存在。
  4. 您的案例类的类型数据通过WeakTypeTag. 这评估为具有大量模式匹配且没有我能找到的文档的 Scala 结构。
  5. 我们再次假设只有一个(“主”?)构造函数,但我认为 Scala 中定义的所有类都只能有一个构造函数。由于这种技术检查的是构造函数的字段,而不是类中的所有 JVM 字段,因此它不会因缺乏通用性而影响我之前的解决方案。
  6. 它使用准引号来建立一个匿名的、具体的CaseClassFieldsExtractor.
  7. 所有这些“隐式”业务都允许定义宏并将其包装在函数调用 ( caseClassFields) 中,而不会在尚未定义时过早调用。

欢迎任何可以改进此解决方案或解释“隐含”如何准确地完成他们所做的事情(或者是否可以删除)的评论。

于 2016-02-10T02:00:04.263 回答