2010-02-08 181 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