【问题标题】:Get only super class fields只获取超类字段
【发布时间】:2023-03-19 00:11:01
【问题描述】:
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

我有这两个课程。实际上,两者都有很多领域。在某个地方,我只需要超类的字段,取自 Person 类。

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

*我很确定我会在为类 Details 写入 json 时遇到一些问题(这不是案例类,也没有单例对象,但这是另一个主题)

【问题讨论】:

  • 你的println(details)实际上是println(details.toString),这个toString实现来自instanceclass。在这里,你的instance 的实际classPerson,所以它会使用它。这正是继承的工作原理。此外,您的班级Details 没有任何字段。您可以通过val d = new Details(1, "Europe") 创建Details 的实例来检查,您将无法访问d.aged.address,因为它没有任何字段成员。
  • 也许这行得通? val details = person.getClass.getSuperclass ?
  • @Ben 这将为您提供Person 的运行时超类。您认为使用运行时类可以做什么?
  • @AlleXys 你能发布一个预期输出的例子吗?
  • “获取字段”是什么意思?只打印他们的内容?

标签: scala inheritance extends case-class


【解决方案1】:

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

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

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

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

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] = ...
}

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

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

对于第二点,我们可以遍历父类,然后只获取公共访问器:

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。所以,首先我们只取值名称:

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")
    }

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

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 引入了一个更安全、更干净的宏系统,如果你使用它并想更进一步,可以阅读这些文章:

【讨论】:

    【解决方案2】:

    如果我正确地回答了您的问题,那么您可能会问我运行时多态性或来自 java 的动态方法分派。 如果是这样,您可能必须同时创建类而不是案例类

    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 对象并引用超类(详情)

    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)
    

    【讨论】:

    • 因为我有一个超过 80 列的 mysql 表,并且我试图拥有一个实体(或域),所以我遇到了这个问题。因此,我的存储库返回一个包含 80 个字段的类(与 db 表字段完全相同)。为了不修改它,我想我需要手动将 repo 结果映射到一个新的案例类中,其中 Details 作为 Person 内部的嵌套类(这将是服务作业)。
    • 是的,尽量保持分组数据的嵌套结构,这样JSON解析对你来说也很容易。
    • 我检查了 Amit 的回答,因为我在我的逻辑中发现了一个问题,他的回答解决了它。我需要将 Person 类的先前值与新值(每 30 秒请求一次)进行比较。但在 Scala 中,似乎我无法比较一个类的 2 个实例(使用 ==equals()),所以我需要将它映射到另一个带有嵌套字段(嵌套对象)的案例类中,然后比较值。很棒的 Scala:D 我发现我可以覆盖 equals 方法,但我不想要它。
    • 我不需要手动检查每个字段(if new.field1 == old.field2 && new.field2 == old.field2 && .. 等等)。在 javascript 中,可以执行 class.fields.forEach(field => newClass[field] == oldClass[field]) 之类的操作。这是 Scala 中的一种反映,现在离我太远了:d
    • 是的,这是正确的,但最终,您将使用所有 scala 语法糖到达那里。小伙伴学习愉快! :)
    猜你喜欢
    • 1970-01-01
    • 2017-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-20
    相关资源
    最近更新 更多