Отражение на классе класса Scala
Я пытаюсь написать признак (в Scala 2.8), который можно смешать с классом case, позволяя проверять его поля во время выполнения, для конкретной цели отладки. Я хочу вернуть их в том порядке, в котором они были объявлены в исходном файле, и я хотел бы опустить любые другие поля внутри класса 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
Можно ли это реализовать?
Ответы
Ответ 1
В каждом примере я видел, что поля находятся в обратном порядке: последний элемент в массиве getFields является первым, указанным в классе case. Если вы используете классы классов "красиво", тогда вы должны просто отобразить productElement(n)
на getDeclaredFields()( getDeclaredFields.length-n-1)
.
Но это довольно опасно, так как я ничего не знаю в спецификации, которая настаивает на том, что это должно быть так, и если вы переопределите val в классе case, он даже не появится в getDeclaredFields (это появится в полях этого суперкласса).
Вы можете изменить свой код, чтобы предположить, что это так, но убедитесь, что метод getter с этим именем и productIterator возвращают одинаковое значение и генерируют исключение, если они этого не делают (что означает, что вы на самом деле не знаете что соответствует чему).
Ответ 2
Посмотрите в багажник, и вы найдете это. Прослушать комментарий, это не поддерживается: но так как мне также нужны эти имена...
/** 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
}
}
Ответ 3
Здесь короткая и рабочая версия, основанная на примере выше
trait CaseClassReflector extends Product {
def getFields = getClass.getDeclaredFields.map(field => {
field setAccessible true
field.getName -> field.get(this)
})
}
Ответ 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
Изменить: подсказки roland и virtualeyes
Необходимо включить библиотеку scalap
, которая является частью scala -lang collection.
Спасибо за ваши подсказки, roland и virtualeyes.