Получение параметров case-класса через Reflection

В продолжение вопроса Мэтта Р., поскольку Scala 2.10 отсутствует для довольно много времени, как лучше всего извлечь поля и значения класса дела. Возьмем аналогичный пример:

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

val RBG = Colour(1,3,"isBlue")

Я хочу получить список (или массив, или любой итератор, если на то пошло), в котором поля были бы объявлены в конструкторе как значения кортежа, подобные этим:

[(red, 1),(green, 3),(blue, "isBlue")]

Я знаю тот факт, что в сети есть много примеров по той же проблеме, но, как я уже сказал, я хотел знать, какой должен быть наиболее идеальный способ извлечения необходимой информации.


person Core_Dumped    schedule 09.12.2013    source источник
comment
поскольку классы case реализуют черту Product, вы должны иметь возможность использовать метод Product: productIterator вместе с zipwithindex.   -  person Stefan Kunze    schedule 09.12.2013


Ответы (2)


Если вы используете отражение Scala 2.10, этот ответ - половина того, что вам нужно. Он предоставит вам символы методов класса case, чтобы вы знали порядок и имена аргументов:

import scala.reflect.runtime.{universe => ru}
import ru._

def getCaseMethods[T: TypeTag] = typeOf[T].members.collect {
  case m: MethodSymbol if m.isCaseAccessor => m
}.toList

case class Person(name: String, age: Int)

getCaseMethods[Person]  // -> List(value age, value name)

Вы можете вызвать .name.toString для этих методов, чтобы получить соответствующие имена методов.

Следующим шагом является вызов этих методов для данного экземпляра. Для этого вам понадобится зеркало времени выполнения

val rm = runtimeMirror(getClass.getClassLoader)

Затем вы можете "отзеркалить" реальный экземпляр:

val p  = Person("foo", 33)
val pr = rm.reflect(p)

Затем вы можете поразмыслить над pr каждым методом, используя reflectMethod, и выполнить его через apply. Не проходя по каждому шагу отдельно, вот общее решение (см. Строку val value = для получения информации о механизме извлечения значения параметра):

def caseMap[T: TypeTag: reflect.ClassTag](instance: T): List[(String, Any)] = {
  val im = rm.reflect(instance)
  typeOf[T].members.collect {
    case m: MethodSymbol if m.isCaseAccessor =>
      val name  = m.name.toString
      val value = im.reflectMethod(m).apply()
      (name, value)
  } (collection.breakOut)
}

caseMap(p) // -> List(age -> 33, name -> foo)
person 0__    schedule 09.12.2013

Каждый объект case является продуктом, поэтому вы можете использовать итератор для получения имен всех его параметров и другой итератор для получения значений всех его параметров:

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

val rgb = Colour(1, 3, "isBlue")

val names = rgb.productElementNames.toList  // List(red, green, blue)
val values = rgb.productIterator.toList     // List(1, 3, isBlue)

names.zip(values).foreach(print)            // (red,1)(green,3)(blue,isBlue)

Под продуктом я подразумеваю как декартово произведение, так и экземпляр Продукт. Для этого требуется Scala 2.13.0; хотя Продукт был доступен раньше, итератор для получения имен элементов был добавлен только в версии 2.13.0.

Обратите внимание, что отражение не требуется.

person Fábio Pakk Selmi-Dei    schedule 20.06.2019
comment
Как получить типы? - person Jus12; 18.12.2019