仅获取超级 class 字段

Get only super class fields

case class Person(name: String, 
                  override val age: Int, 
                  override val address: String
    ) extends Details(age, address)

class Details(val age: Int, val address: String)

val person = Person("Alex", 33, "Europe")

val details = person.asInstanceOf[Details] // ??? 
println(details) // I want only Details class fields

我有这 2 个 classes。实际上,两者都有很多领域。在某个地方,我只需要 superclass 的字段,取自 Person class.

有一个很好的方法可以只获取超 class 值而不是逐个字段映射它们吗?

*我很确定 json 写 class 细节会遇到一些问题(这不是 class 的情况,也没有一个单例对象,但这是另一个主题)

如果我答对了你的问题,那么你可能是在问我 java 的运行时多态性或动态方法分派。 如果是这样,您可能必须同时创建 class 而不是大小写 class

class Details( val age: Int,  val address: String)

 class Person(name: String,
                  override val age: Int,
                  override val  address: String
                 ) extends Details(age, address) {

}

现在创建person对象并引用superclass(详情)

val detail:Details =  new Person("Alex", 33, "Europe")


println(detail.address)
println(detail.age)

这样你就可以获得唯一的addressage

另一种方法是,为什么我们不能创建 Details 一个单独的实体,例如:

case class Details(  age: Int,   address: String)

 case class Person(name: String,
                   details: Details
                 )


val detail =   Person("Alex", Details(10,"Europe") )

输出:

println(detail.details)

Details(10,Europe)

我将 post 一个利用 Scala 宏系统的解决方案(旧类型,不是 Scala 3.0 引入的最新版本)。这对你来说可能有点矫枉过正......

顺便说一句,如果你只想访问父值(例如获取键值对),你可以:

  1. 给定一个类型标签,得到所有父类;
  2. 从中提取所有访问器(vals);
  3. 对于每个 val,获取它的值;
  4. 最后 return 是一个包含所有访问器的列表

所以,我试着一步步解决每一个问题。 首先,我们要把宏定义写成:

object Macros {
  def accessors[T](element : T): String = macro MacrosImpl.accessors[T]
}

object MacrosImpl {
  def accessors[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[String] = ...
}

对于第一点,我们可以利用反射宏编程 API 使用 c.universe:

import c.universe._
val weakType = weakTypeTag[T] //thanks to the WeakTypeTag typeclass
val parents = weakType.tpe.baseClasses

对于第二点,我们可以遍历父 类 然后只采用 public 访问器:

val accessors = parents
      .map(weakType.tpe.baseType(_))
      .flatMap(_.members)
      .filter(_.isPublic)
      .filter(_.isMethod)
      .map(_.asMethod)
      .filter(_.isAccessor)
      .toSet

因此,例如,如果我们写 Macros.accessors[Details](person)accessors 将产生 ageaddress

要取值,我们可以利用quasiqouting。所以,首先我们只取值 name:

val names = accessors
      .map(_.fullName)
      .map(_.split("\."))
      .map(_.reverse.head)

然后我们将它们转换成TermName:

val terms = names.map(TermName(_))

最后,我们将每个术语转换为包含 val 名称及其值的键值元组:

val accessorValues = terms
   .map(name => c.Expr[(String, Any)](q"(${name.toString}, ${element}.${name})"))
   .toSeq

最后一步是将 Seq[Expr[(String, Any)] 转换为 Expr[Seq[(String, Any)]。一种方法是利用递归、reifysplicing 表达式:

def seqToExprs(seq: Seq[Expr[(String, Any)]]): c.Expr[Seq[(String, Any)]] =
  seq.headOption match {
      case Some(head) => 
        c.universe.reify(
          Seq((head.splice._1, head.splice._2)) ++
          seqToExprs(seq.tail).splice
        )
      case _ => c.Expr[Seq[(String, Any)]](q"Seq.empty")
    }

所以现在我决定 return 一个字符串表示(但你可以随意操作它):

val elements = seqToExprs(accessorValues)
c.Expr[String](q"${elements}.mkString")

您可以将其用作:

import Macros._
class A(val a : Int)
class B(val b : Int) extends A(b)
class C(val c: Int) extends B(c)
//println(typeToString[List[Set[List[Double]]]])
val c = new C(10)
println(accessors[C](c)) // prints (a, 10)(b, 10)(c, 10)
println(accessors[B](c)) // prints (a, 10)(b, 10)
println(accessors[A](c)) // prints (a, 10)

并且,使用您的示例:

// Your example:
  case class Person(name: String,
                    override val age: Int,
                    override val address: String
                   ) extends Details(age, address)

  class Details(val age: Int, val address: String)

  val person = Person("Alex", 33, "Europe")
  println(accessors[Details](person)) // prints (address,Europe)(age,33)
  println(accessors[Person](person)) // prints (address,Europe)(age,33)(name,Alex)

Here 有一个实现了宏的存储库。

Scala 3.0 引入了一个更安全更干净的宏系统,如果你使用它并且想更进一步可以阅读这些文章: