Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a decent toString() method in scala using reflection?

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.

like image 404
Justin W Avatar asked Jan 06 '10 21:01

Justin W


4 Answers

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.

like image 134
Lytol Avatar answered Sep 18 '22 12:09

Lytol


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.

like image 24
Randall Schulz Avatar answered Sep 18 '22 12:09

Randall Schulz


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")
like image 23
retronym Avatar answered Sep 18 '22 12:09

retronym


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();
like image 24
psp Avatar answered Sep 18 '22 12:09

psp