Question

To make debug-time introspection into classes easy, I'd like to make a generic toString method in the base class for the objects in question. As it's not performance critical code, I'd like to use Reflection to print out field name/value pairs ("x=1, y=2" etc).

Is there an easy way to do this? I tried several potential solutions, and ran up against security access issues, etc.

To be clear, the toString() method in the base class should reflectively iterate over public vals in any classes that inherit from it, as well as any traits that are mixed in.

Was it helpful?

Solution

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")

OTHER TIPS

Example:

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

Uses Java Reflection API, so don't forget to import java.lang.reflect._

Also, you may need to catch IllegalAccessException on the field.get(this) calls in some scenarios, but this is just meant as a starting point.

Are you aware the Scala case classes get these compiler-generated methods:

  • toString(): String
  • equals(other: Any): Boolean
  • hashCode: Int

They also get companion objects for "new-less" constructors and pattern matching.

The generated toString() is pretty much like the one you describe.

Scala doesn't generate any public fields. They're all going to be private. The accessor methods are what will be public, reflect upon those. Given a class like:

class A {
  var x = 5
}

The generated bytecode looks like:

private int x;
public void x_$eq(int);
public int x();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top