对 Scala 案例类的反思

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2224251/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:55:53  来源:igfitidea点击:

Reflection on a Scala case class

reflectionscalascala-2.8case-class

提问by Matt R

I'm trying to write a trait (in Scala 2.8) that can be mixed in to a case class, allowing its fields to be inspected at runtime, for a particular debugging purpose. I want to get them back in the order that they were declared in the source file, and I'd like to omit any other fields inside the case class. For example:

我正在尝试编写一个特征(在 Scala 2.8 中),它可以混合到一个案例类中,允许在运行时检查其字段,用于特定的调试目的。我想按照它们在源文件中声明的顺序取回它们,并且我想省略 case 类中的任何其他字段。例如:

trait CaseClassReflector extends Product {

  def getFields: List[(String, Any)] = {
    var fieldValueToName: Map[Any, String] = Map()
    for (field <- getClass.getDeclaredFields) {
      field.setAccessible(true)
      fieldValueToName += (field.get(this) -> field.getName) 
    }
    productIterator.toList map { value => fieldValueToName(value) -> value }
  }

}

case class Colour(red: Int, green: Int, blue: Int) extends CaseClassReflector {
  val other: Int = 42
}

scala> val c = Colour(234, 123, 23)
c: Colour = Colour(234,123,23)

scala> val fields = c.getFields    
fields: List[(String, Any)] = List((red,234), (green,123), (blue,23))

The above implementation is clearly flawed because it guesses the relationship between a field's position in the Product and its name by equality of the value on those field, so that the following, say, will not work:

上面的实现显然是有缺陷的,因为它通过这些字段上的值的相等性来猜测一个字段在 Product 中的位置与其名称之间的关系,因此以下内容将不起作用:

Colour(0, 0, 0).getFields

Is there any way this can be implemented?

有什么办法可以实现吗?

采纳答案by Rex Kerr

In every example I've seen the fields are in reverse order: the last item in the getFields array is the first one listed in the case class. If you use case classes "nicely", then you should just be able to map productElement(n)onto getDeclaredFields()( getDeclaredFields.length-n-1).

在我见过的每个示例中,字段的顺序是相反的:getFields 数组中的最后一项是 case 类中列出的第一项。如果您“很好地”使用案例类,那么您应该能够映射productElement(n)getDeclaredFields()( getDeclaredFields.length-n-1).

But this is rather dangerous, as I don't know of anything in the spec that insists that it must be that way, and if you override a val in the case class, it won't even appear in getDeclaredFields (it'll appear in the fields of that superclass).

但这是相当危险的,因为我不知道规范中的任何内容坚持必须那样做,并且如果您覆盖 case 类中的 val,它甚至不会出现在 getDeclaredFields 中(它会出现在该超类的字段中)。

You might change your code to assume things are this way, but check that the getter method with that name and the productIterator return the same value and throw an exception if they don't (which means that you don't actually know what corresponds to what).

您可能会更改您的代码以假设事情是这样的,但请检查具有该名称的 getter 方法和 productIterator 是否返回相同的值,如果没有则抛出异常(这意味着您实际上不知道对应于什么什么)。

回答by psp

Look in trunk and you'll find this. Listen to the comment, this is not supported: but since I also needed those names...

看看后备箱,你会发现这个。听评论,这是不支持的:但因为我也需要这些名字......

/** private[scala] so nobody gets the idea this is a supported interface.
 */
private[scala] def caseParamNames(path: String): Option[List[String]] = {
  val (outer, inner) = (path indexOf '$') match {
    case -1   => (path, "")
    case x    => (path take x, path drop (x + 1))
  }

  for {
    clazz <- getSystemLoader.tryToLoadClass[AnyRef](outer)
    ssig <- ScalaSigParser.parse(clazz)
  }
  yield {
    val f: PartialFunction[Symbol, List[String]] =
      if (inner.isEmpty) {
        case x: MethodSymbol if x.isCaseAccessor && (x.name endsWith " ") => List(x.name dropRight 1)
      }
      else {
        case x: ClassSymbol if x.name == inner  =>
          val xs = x.children filter (child => child.isCaseAccessor && (child.name endsWith " "))
          xs.toList map (_.name dropRight 1)
      }

    (ssig.symbols partialMap f).flatten toList
  }
}

回答by Sagie Davidovich

Here's a short and working version, based on the example above

这是一个简短的工作版本,基于上面的例子

  trait CaseClassReflector extends Product {
    def getFields = getClass.getDeclaredFields.map(field => {
      field setAccessible true
      field.getName -> field.get(this)
    })
  }

回答by Stefan Endrullis

You can also use the ProductCompletionfrom the interpreter package to get to attribute names and values of case classes:

您还可以使用ProductCompletion来自解释器包的 来获取案例类的属性名称和值:

import tools.nsc.interpreter.ProductCompletion

// get attribute names
new ProductCompletion(Colour(1, 2, 3)).caseNames
// returns: List(red, green, blue)

// get attribute values
new ProductCompletion(Colour(1, 2, 3)).caseFields


Edit: hints by roland and virtualeyes

编辑:roland 和 virtualeyes 的提示

It is necessary to include the scalaplibrary which is part of the scala-lang collection.

有必要包含scalap属于scala-lang 集合的库

Thanks for your hints, roland and virtualeyes.

感谢您的提示,roland 和 virtualeyes。