2010-02-08 137 views
12

我想写一个特征(斯卡拉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)) 

以上实现显然是有缺陷的,因为它会猜测产品的一个领域的地位,它的名字由这些字段中的值的平等之间的关系,这样下,比如说,将无法正常工作:

Colour(0, 0, 0).getFields 

这有什么办法可以实现吗?

+0

有在你的代码中的错误的例子。值不是唯一的,因此当移动时,您将使用(field.get(this) - > field.getName)覆盖值,而不是一个字段具有给定名称。 请参阅下面的代码的重写版本。 – 2013-03-27 10:33:09

+0

@SagieDavidovich事实上,如上所述,“上述实现显然存在缺陷” – 2013-03-27 10:46:38

回答

7

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

但是这是相当危险的,因为我不知道规范中的任何内容,坚持认为它必须是这种方式,并且如果在case类中重写val,它甚至不会出现在getDeclaredFields(它会出现在那个超类的字段中)。

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

+1

我面临与Matt R面临的相同问题。作为斯卡拉的相对小手,你能否解释一下你的答案。这会很有帮助。谢谢! – 2013-12-06 15:20:19

+2

@Core_Dumped - 事实上,我认为你很可能会陷入麻烦,而不是想出解决问题的办法,除非你可以使用我的模糊提示来制定你自己的解决方案。正如我所说,“这是相当危险的”。您有责任预测和避免这些问题,这可能需要至少在这方面从“相对noob”转变为“not”。在REPL中使用反射和示例案例类,并查看是否可以搞清楚! – 2013-12-15 21:31:32

+0

getClass.getDeclaredFields.map(_。getName).zip(productIterator.toList).toMap – 2016-04-21 11:58:35

10

看看树干,你会发现这一点。听意见,这是不支持:但是因为我还需要那些名字......

/** 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 
    } 
} 
4

您还可以使用ProductCompletion从解释包去属性case类的名称和值:

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 

编辑:提示罗兰和virtualeyes

有必要包括scalap图书馆是scala-lang collection的一部分。

感谢您的提示,罗兰和virtualeyes。

+1

请注意,对'caseNames'的调用仅适用于scalap(http://www.scala-lang.org/node/292)可以在类路径中找到。否则将返回一个空列表(当使用Scala 2.9.1时)。 – 2012-02-03 14:56:13

+1

+1 @roland,你说的是真的。鉴于scalap下载并没有完全在谷歌搜索中跳跃出来,这里是2.9.1:https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/scalap/2.9。 1/ – virtualeyes 2012-02-12 18:18:01

+0

注意需要案例类实例的catch 22才能够反映它。希望2.10会带来这样的商品,在这方面的话,双手绑在2.9,与黑盒案例类是一个PITA,结束输入域模型,ORM映射和验证一式三份,wtf ... – virtualeyes 2012-02-20 18:34:44

9

这里有一个短期和工作版本,基于上述

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

I试过这个,它起作用了,至少对于一个简单的案例类。 – 2013-03-28 23:31:36