Как создать достойный метод toString () в Scala, используя отражение? - PullRequest
6 голосов
/ 07 января 2010

Чтобы облегчить самоанализ времени отладки для классов, я хотел бы создать общий метод toString в базовом классе для рассматриваемых объектов. Поскольку это не критичный к производительности код, я хотел бы использовать Reflection для распечатки пар имя / значение поля ("x = 1, y = 2" и т. Д.).

Есть ли простой способ сделать это? Я испробовал несколько возможных решений и столкнулся с проблемами безопасности доступа и т. Д.

Для ясности, метод toString () в базовом классе должен рефлексивно перебирать общедоступные значения в любых классах, которые его наследуют, а также в любых чертах, которые смешаны.

Ответы [ 4 ]

6 голосов
/ 07 января 2010

Пример:

override def toString() = {
  getClass().getDeclaredFields().map { field:Field => 
    field.setAccessible(true)
    field.getName() + ": " + field.getType() + " = " + field.get(this).toString()
  }.deepMkString("\n")
}

Использует Java Reflection API, поэтому не забудьте импортировать java.lang.reflect ._

Кроме того, вам может понадобиться перехватить IllegalAccessException на вызовах field.get (this) в некоторых сценариях, но это просто означает отправную точку.

4 голосов
/ 09 января 2010

Знаете ли вы, что классы дел Scala получают следующие методы, сгенерированные компилятором:

  • toString (): String
  • равно (прочее: любое): логическое значение
  • hashCode: Int

Они также получают сопутствующие объекты для конструкторов "new-less" и сопоставления с образцом.

Сгенерированный toString() очень похож на тот, который вы описываете.

2 голосов
/ 09 января 2010
import util._                 // For Scala 2.8.x NameTransformer
import scala.tools.nsc.util._ // For Scala 2.7.x NameTransformer

/**
 * Repeatedly run `f` until it returns None, and assemble results in a Stream.
 */
def unfold[A](a: A, f: A => Option[A]): Stream[A] = {
  Stream.cons(a, f(a).map(unfold(_, f)).getOrElse(Stream.empty))
}

def get[T](f: java.lang.reflect.Field, a: AnyRef): T = {
  f.setAccessible(true)
  f.get(a).asInstanceOf[T]
}

/**
 * @return None if t is null, Some(t) otherwise.
 */
def optNull[T <: AnyRef](t: T): Option[T] = if (t eq null) None else Some(t)

/**
 * @return a Stream starting with the class c and continuing with its superclasses.
 */
def classAndSuperClasses(c: Class[_]): Stream[Class[_]] = unfold[Class[_]](c, (c) => optNull(c.getSuperclass))

def showReflect(a: AnyRef): String = {
  val fields = classAndSuperClasses(a.getClass).flatMap(_.getDeclaredFields).filter(!_.isSynthetic)
  fields.map((f) => NameTransformer.decode(f.getName) + "=" + get(f, a)).mkString(",")
}

// TEST
trait T {
  val t1 = "t1"
}

class Base(val foo: String, val ?? : Int) {
}

class Derived(val d: Int) extends Base("foo", 1) with T

assert(showReflect(new Derived(1)) == "t1=t1,d=1,??=1,foo=foo")
2 голосов
/ 08 января 2010

Scala не генерирует открытые поля. Они все будут частными. Методы доступа - это то, что будет публичным, размышляйте о них. Учитывая класс как:

class A {
  var x = 5
}

Сгенерированный байт-код выглядит так:

private int x;
public void x_$eq(int);
public int x();
...