2010-02-08 6 views
12

Я пытаюсь написать черту (в Scala 2.8), которая может быть смешана с классом case, позволяя проверять его поля во время выполнения, для определенной цели отладки. Я хочу вернуть их в том порядке, в котором они были объявлены в исходном файле, и я хотел бы опустить любые другие поля внутри класса case. Например:Отражение класса класса Scala

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) при перемещении, чем одно поле имеет указанное имя. См. Переписанную версию вашего кода ниже. –

+0

@SagieDavidovich Действительно, как уже отмечалось, «вышеупомянутая реализация явно ошибочна» –

ответ

7

В каждом примере я видел, что поля находятся в обратном порядке: последний элемент в массиве getFields является первым, указанным в классе case. Если вы используете классы классов «красиво», тогда вы должны просто указать productElement(n) на getDeclaredFields()(getDeclaredFields.length-n-1).

Но это довольно опасно, поскольку я ничего не знаю в спецификации, которая настаивает на том, что это должно быть именно так, и если вы переопределите val в классе case, это даже не появится в getDeclaredFields (он появится в полях этого суперкласса).

Вы можете изменить свой код, чтобы предположить, что это так, но убедитесь, что метод getter с этим именем и productIterator возвращают одно и то же значение и генерируют исключение, если они этого не делают (это означает, что вы фактически не используете знать, что соответствует чему).

+1

Я столкнулся с той же проблемой, с которой сталкивался Matt R. Будучи относительным noob в Scala, не могли бы вы объяснить свой ответ немного больше. Это было бы очень полезно. Благодаря! –

+2

@Core_Dumped - На самом деле, я думаю, что у вас больше шансов попасть в проблему, чем придумать решение вашей проблемы, если вы не можете использовать мои смутные намеки, чтобы создать собственное решение. Как я уже сказал, «это довольно опасно».Вы несете ответственность за то, чтобы иметь возможность предвидеть и избегать проблем, которые могут потребовать перехода от «относительного нуба» к «не» по крайней мере в этом аспекте. Играйте с рефлексией и примерами классов классов в REPL и посмотрите, сможете ли вы это понять! –

+0

getClass.getDeclaredFields.map (_. GetName) .zip (productIterator.toList) .toMap –

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 из пакета интерпретатора, чтобы добраться до имен атрибутов и значений тематических классов:

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: советы Роланда и virtualeyes

необходимо включать scalap библиотека, которая является частью scala-lang collection.

Благодарим за ваши намеки, роланд и virtualeyes.

+1

Обратите внимание, что вызов 'caseNames' работает только в том случае, если scalap (http://www.scala-lang.org/node/292) можно найти на пути к классам. В противном случае возвращается пустой список (при использовании Scala 2.9.1). –

+1

+1 @roland, это правда, что вы говорите. Учитывая, что скайп-скачка не точно выпрыгивает из поиска в google, здесь это для 2.9.1: https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/scalap/2.9. 1/ – virtualeyes

+0

обратите внимание на уловку 22, требующую экземпляра класса case, прежде чем сможете задуматься. Надеюсь, 2.10 привезет товар так сказать в этом плане, руки привязаны к 2.9, работа с классами ящиков черного ящика - это PITA, завершение ввода модели домена, картирование ORM и валидация в трех экземплярах, wtf ... – virtualeyes

9

Вот короткий и рабочий вариант, основанный на приведенном выше примере

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

I попробовал это, и это сработало, по крайней мере, для простого класса case. –