Получить только поля супер класса

#scala #inheritance #extends #case-class

Вопрос:

 case class Person(name: String, 
                  override val age: Int, 
                  override val address: String
    ) extends Details(age, address)

class Details(val age: Int, val address: String)

val person = Person("Alex", 33, "Europe")

val details = person.asInstanceOf[Details] // ??? 
println(details) // I want only Details class fields
 

У меня есть эти 2 занятия. На самом деле у обоих есть много областей. Где-то мне нужно только поле суперкласса, взятое из класса Персон.

Есть хороший способ получить только значения суперкласса и не сопоставлять их поле за полем?

*Я почти уверен, что у меня возникнут некоторые проблемы с написанием json для сведений о классе (который не является классом вариантов и не имеет одноэлементного объекта, но это другая тема)

Комментарии:

1. Ваш println(details) на самом деле println(details.toString) , и эта toString реализация исходит от самого class instance . Вот, собственно class , что у вас instance есть Person , так что оно это и будет использовать. Именно так работает наследование. Кроме того, в вашем классе Details нет никаких полей. Вы можете проверить , создав экземпляр Details by val d = new Details(1, "Europe") , к которому вы не сможете получить доступ d.age , или d.address поскольку в нем нет элемента поля.

2. может быть, это сработает? val details = person.getClass.getSuperclass ?

3. @Бен, который даст вам супер-класс времени выполнения Person . Как вы думаете, что можно сделать с помощью класса времени выполнения ?

4. @AlleXys можете ли вы опубликовать пример того, каким должен быть ожидаемый результат?

5. Что значит «получить поля»? Просто распечатайте их содержимое?

Ответ №1:

Если я правильно понял ваш вопрос, то вы, возможно, спрашиваете меня о полиморфизме времени выполнения или отправке динамического метода из java. Если это так, вам, возможно, придется создать как класс, так и класс без регистра

 class Details( val age: Int,  val address: String)

 class Person(name: String,
                  override val age: Int,
                  override val  address: String
                 ) extends Details(age, address) {

}
 

Теперь создайте объект person и ссылку на суперкласс (Подробности)

 val detail:Details =  new Person("Alex", 33, "Europe")


println(detail.address)
println(detail.age)
 

Таким образом, вы сможете получить единственную address и age

Другой способ-это , например, почему мы не можем создать Details отдельную сущность, такую как:

 case class Details(  age: Int,   address: String)

 case class Person(name: String,
                   details: Details
                 )


val detail =   Person("Alex", Details(10,"Europe") )

 

Выход:

 println(detail.details)

Details(10,Europe)
 

Комментарии:

1. Поскольку у меня есть таблица mysql с более чем 80 столбцами, и я пытался создать сущность (или домен), у меня возникла эта проблема. Итак, мой репозиторий возвращает класс с 80 полями (точно так же, как поля таблицы БД). Чтобы не изменять его, я думаю, что мне нужно будет вручную отобразить результат репо в новом классе case с деталями в виде вложенного класса внутри Person (это будет служебное задание).

2. Да, старайтесь всегда сохранять вложенную структуру для сгруппированных данных, таким образом, анализ JSON также будет для вас легким.

3. Я проверил ответ Амита, потому что нашел проблему в своей логике, и его ответ решил ее. Мне нужно сравнить предыдущее значение класса Person с новым значением (запросы бд каждые 30 секунд). Но в Scala, похоже, я не могу сравнить 2 экземпляра класса (с == или equals() ), поэтому мне нужно сопоставить его в другом классе case с вложенными полями (вложенными объектами), а затем сравнить значения. Потрясающая Scala 😀 Я обнаружил, что могу переопределить метод equals, но я этого не хочу.

4. Мне не нужно вручную проверять каждое поле ( if new.field1 == old.field2 amp;amp; new.field2 == old.field2 amp;amp; .. и так далее). В javascript можно сделать что-то подобное class.fields.forEach(field => newClass[field] == oldClass[field]) . Это что-то из отражения в Scala и сейчас слишком далеко от меня :d

5. Да, это верно, но в конечном итоге вы доберетесь туда со всем синтаксическим сахаром scala. Счастливого обучения, приятель! 🙂

Ответ №2:

Я опубликую решение, которое использует макросистему scala (старого типа, а не новейшую, представленную в Scala 3.0). Для тебя это может быть перебором…

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

  1. учитывая тег типа, получите всех родителей;
  2. из них извлеките все средства доступа (vals);
  3. для каждого значения получите его значение;
  4. и, наконец, возвращает список со всеми выбранными способами доступа

Поэтому я стараюсь решать каждый пункт шаг за шагом. Прежде всего, мы должны записать определение макроса как:

 object Macros {
  def accessors[T](element : T): String = macro MacrosImpl.accessors[T]
}

object MacrosImpl {
  def accessors[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[String] = ...
}

 

для первого пункта мы можем использовать API макропрограммирования отражения, используя c.universe :

 import c.universe._
val weakType = weakTypeTag[T] //thanks to the WeakTypeTag typeclass
val parents = weakType.tpe.baseClasses
 

для второго пункта мы можем повторить родительские классы, а затем использовать только общедоступные методы доступа:

 val accessors = parents
      .map(weakType.tpe.baseType(_))
      .flatMap(_.members)
      .filter(_.isPublic)
      .filter(_.isMethod)
      .map(_.asMethod)
      .filter(_.isAccessor)
      .toSet
 

Так, например, если мы напишем Macros.accessors[Details](person) , accessors получится age и address .

Чтобы получить эту ценность, мы можем использовать квазигаутинг. Итак, сначала мы берем только имя значений:

 val names = accessors
      .map(_.fullName)
      .map(_.split("\."))
      .map(_.reverse.head)
 

Затем мы преобразуем их в TermName :

 val terms = names.map(TermName(_))
 

И, наконец, мы преобразуем каждый термин в кортеж значений ключа, содержащий имя val и его значение:

 val accessorValues = terms
   .map(name => c.Expr[(String, Any)](q"(${name.toString}, ${element}.${name})"))
   .toSeq
 

Последний шаг состоит в преобразовании a Seq[Expr[(String, Any)] в a Expr[Seq[(String, Any)] . Способ сделать это может заключаться в использовании рекурсии reify и splicing выражения:

 def seqToExprs(seq: Seq[Expr[(String, Any)]]): c.Expr[Seq[(String, Any)]] =
  seq.headOption match {
      case Some(head) => 
        c.universe.reify(
          Seq((head.splice._1, head.splice._2))   
          seqToExprs(seq.tail).splice
        )
      case _ => c.Expr[Seq[(String, Any)]](q"Seq.empty")
    }
 

Поэтому теперь я решаю вернуть строковое представление (но вы можете манипулировать им так, как хотите).:

 val elements = seqToExprs(accessorValues)
c.Expr[String](q"${elements}.mkString")
 

Вы можете использовать его как:

 import Macros._
class A(val a : Int)
class B(val b : Int) extends A(b)
class C(val c: Int) extends B(c)
//println(typeToString[List[Set[List[Double]]]])
val c = new C(10)
println(accessors[C](c)) // prints (a, 10)(b, 10)(c, 10)
println(accessors[B](c)) // prints (a, 10)(b, 10)
println(accessors[A](c)) // prints (a, 10)
 

И, используя ваш пример:

 // Your example:
  case class Person(name: String,
                    override val age: Int,
                    override val address: String
                   ) extends Details(age, address)

  class Details(val age: Int, val address: String)

  val person = Person("Alex", 33, "Europe")
  println(accessors[Details](person)) // prints (address,Europe)(age,33)
  println(accessors[Person](person)) // prints (address,Europe)(age,33)(name,Alex)
 

Здесь есть репозиторий с реализованным макросом.

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