2010-01-06 13 views
6

Pour faciliter l'introspection de débogage dans les classes, j'aimerais créer une méthode toString générique dans la classe de base pour les objets en question. Comme ce n'est pas un code de performance critique, j'aimerais utiliser Reflection pour imprimer les paires nom/valeur de champ ("x = 1, y = 2", etc.).Comment créer une méthode toString() correcte dans Scala en utilisant la réflexion?

Y at-il un moyen facile de faire cela? Pour être clair, la méthode toString() de la classe de base devrait refléter de manière réfléchie les vals publics dans toutes les classes qui en héritent, ainsi que la méthode toString() dans la classe de base. tous les traits qui sont mélangés dans

+0

Cela ne devrait-il pas être un trait? 'trait ToString [A]'? – VonC

+0

C'est peut-être la façon la plus propre de le faire, je n'ai pas encore réfléchi à la façon dont cela affecte ma hiérarchie d'objets. J'ai des problèmes d'accès aux propriétés que je ne peux pas expliquer actuellement. –

+0

Les réponses ici, en particulier en utilisant Apache ReflectionToStringBuilder, ne sont pas une mauvaise idée: http://stackoverflow.com/questions/603013/dumping-a-java-objects-properties – ripper234

Répondre

2
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") 
+0

J'aurais dû mentionner que je suis coincé avec 2.7.7 pour le moment. Je n'étais pas sûr de certaines des nouvelles constructions 2.8 que vous avez utilisées dans votre code, et je me suis retrouvé avec une exception de pointeur nul. Cela ressemble à une façon assez propre de le faire bien, alors la réponse va à vous! –

+1

J'ai modifié l'exemple pour être compatible avec 2.7.7. – retronym

6

Exemple:.

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

Utilise Java API de réflexion, il ne faut pas oublier de importation java.lang.reflect._

en outre, vous devrez peut-être à catch IllegalAccessException sur le field.get (this) appelle dans certains scénarios, mais ce n'est qu'un point de départ.

+0

J'ai déjà utilisé quelque chose de similaire, mais Je reçois beaucoup d'exceptions d'accès même pour des choses qui sont publiques. Je suis confus à ce sujet, comme je m'y attendais que si j'avais code comme ceci: class Foo { val bar = 3} que si j'ai appelé, d'une autre classe, Foo.getDeclaredField ("bar"), l'objet de champ résultant aurait le modificateur "public". J'ai utilisé java.lang.reflect.Modifier pour décoder les champs de modificateur, et cela ne fonctionne pas. C'est en 2.7.7, donc je ne suis pas sûr si elle diffère de 2.8 à cet égard. –

+1

J'ai trouvé que 'val obj = field.get (this); val str = obj.asInstanceOf [String] 'ne fonctionne pas.Bizarre –

+1

Cela devrait être mis à jour vers scala 2.10 – ripper234

2

Scala ne génère pas de champs publics. Ils vont tous être privés. Les méthodes d'accès sont ce qui sera public, réfléchissez sur celles-ci. Étant donné une classe comme:

class A { 
    var x = 5 
} 

Le bytecode généré ressemble:

private int x; 
public void x_$eq(int); 
public int x(); 
+0

Je crois qu'il y a longtemps, je le savais déjà. Merci! –

4

Connaissez-vous les classes de cas Scala obtenir ces méthodes générées par le compilateur:

  • toString (): Chaîne
  • est égal à (autre: Tout): Booléen
  • hashCode: Int

Ils reçoivent aussi des objets compagnon pour les constructeurs "nouveau" et moins pattern matching. Le toString() généré ressemble beaucoup à celui que vous décrivez.

+0

Oui, les classes de cas sont magiques. Malheureusement, je ne les utilise pas ici, pour diverses raisons. J'utilise toutes les fonctionnalités que vous avez énumérées dans d'autres endroits, cependant. Je suis sûr que mon trait toString peut gérer les classes de cas dans le futur. Aussi, bon de vous voir sur IRC hier soir, petit monde. –

Questions connexes