考虑以下类和方法:
case class User(id: Long, name: String) {
private var foo = "Foo" // shouldn't be printed
val bar = "bar" // also shouldn't be printed
}
case class Message(id: Long, userId: Long, text: String)
def printInfo[E](o: E)(implicit tt: TypeTag[E]) = {
}
我想让这个方法打印任何案例类的每个字段的名称,类型和值,即
printInfo(User(1, "usr1")) // prints something like "(id, Long, 1), (name, String)"
printInfo(Message(1, 1, "Hello World")) // prints "(id, Long, 1), (userId, Long, 1), (text, String, "Hello World")"
为字段添加一些自定义注释也很可观。
答案 0 :(得分:19)
您可以通过检查类型标记列出的成员并使用其镜像进行反映来完成此操作:
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag
def printInfo[A](a: A)(implicit tt: TypeTag[A], ct: ClassTag[A]): String = {
val members = tt.tpe.members.collect {
case m if m.isMethod && m.asMethod.isCaseAccessor => m.asMethod
}
members.map { member =>
val memberValue = tt.mirror.reflect(a).reflectMethod(member)()
s"(${ member.name }, ${ member.returnType }, $memberValue)"
}.mkString(", ")
}
哪个会这样:
scala> case class User(id: Long, name: String) {
| private var foo = "Foo" // shouldn't be printed
| val bar = "bar" // also shouldn't be printed
| }
defined class User
scala> case class Message(id: Long, userId: Long, text: String)
defined class Message
scala> printInfo(User(1, "usr1"))
res0: String = (name, String, usr1), (id, scala.Long, 1)
scala> printInfo(Message(1, 1, "Hello World"))
res1: String = (text, String, Hello World), (userId, scala.Long, 1), (id, scala.Long, 1)
(如果您想要Long
代替scala.Long
,那么从member.returnType
获得的类型中删除前缀并不会太难,但我会这样做把它作为读者的练习。)
如果没有使用Shapeless进行任何运行时反射,也不难做到这一点:
import shapeless.{ ::, HList, HNil, LabelledGeneric, Typeable, Witness }
import shapeless.labelled.FieldType
trait PrettyPrintable[A] {
def apply(a: A): List[(String, String, String)]
}
object PrettyPrintable {
implicit val hnilPrettyPrintable: PrettyPrintable[HNil] =
new PrettyPrintable[HNil] {
def apply(a: HNil): List[(String, String, String)] = Nil
}
implicit def hconsPrettyPrintable[K <: Symbol, H, T <: HList](implicit
kw: Witness.Aux[K],
ht: Typeable[H],
tp: PrettyPrintable[T]
): PrettyPrintable[FieldType[K, H] :: T] =
new PrettyPrintable[FieldType[K, H] :: T] {
def apply(a: FieldType[K, H] :: T): List[(String, String, String)] =
(kw.value.name, ht.describe, a.head.toString) :: tp(a.tail)
}
implicit def genPrettyPrintable[A, R <: HList](implicit
ag: LabelledGeneric.Aux[A, R],
rp: PrettyPrintable[R]
): PrettyPrintable[A] = new PrettyPrintable[A] {
def apply(a: A): List[(String, String, String)] = rp(ag.to(a))
}
def printInfo[A](a: A)(implicit pp: PrettyPrintable[A]) = pp(a).map {
case (memberName, memberType, memberValue) =>
s"($memberName, $memberType, $memberValue)"
}.mkString(", ")
}
然后:
scala> PrettyPrintable.printInfo(User(1, "usr1"))
res2: String = (id, Long, 1), (name, String, usr1)
scala> PrettyPrintable.printInfo(Message(1, 1, "Hello World"))
res3: String = (id, Long, 1), (userId, Long, 1), (text, String, Hello World)
除此之外,它还为您提供了声明顺序中的字段,我认为应该可以使用类型标记方法,但我尽可能多地避免使用该API,因此我不知道如何做到这一点。不确定。
答案 1 :(得分:0)
现在为Scala 2.13
,case class
(它们是Product
的实现)提供了一种productElementNames方法,该方法返回其字段名称上的迭代器。
与productIterator组合:
// val user = User(1, "user")
(user.productElementNames zip user.productIterator)
.map { case (field, value) => (field, value.getClass.getSimpleName, value) }
.toList
// List[(String, String, Any)] = List((id,Long,1), (name,String,user))
此:
使用Iterator(id, name)
productElementNames
)
使用Iterator(1, user)
productIterator
)
zip
的字段名称和字段值
提取值的类名