views:

214

answers:

6

Let's say I have a class like this (and also further assume that all the private variables:

public class Item {
    private String _id = null;
    private String _name = null;
    private String _description = null;

        ...
}

Now, if I want to build a toString() representation of this class, I would do something like this inside the Item class:

@Override
public String toString() {
    return (_id + " " + _name + " " + _description);
}

But what if I have say 15 private variables inside the class? Do I have to write the name of each and every variable like this?

Ideally, I would like to get over with the task by iterating through the list of private variables of this class and construct the string representation:

@Override
public String toString() {
    ArrayList<String> members = getClass().getMembers(); //Some method like this
    String string = "";
    for(...)
        string += members[i] + " ";
}

Or perhaps a toJSON method, I would still need access to the names of these variables. Any suggestions?

+5  A: 

Check this API org.apache.commons.lang.builder.ToStringBuilder, it provides multiple ways to create toString usinf reflection or without reflection. Take a look at other subclasses as well.

Bhushan
Thanks. This looks very interesting. Especially the method that uses no reflection.
Legend
+2  A: 

Most IDEs provide a way to create a toString method in a given class.

Given an Item class with multiple fields:

class Item {
    int i;
    int j;
    int k;
    int l;
    int m;
    int n;
    int o; 
}

For example, in Eclipse, performing "Generate toString()" feature on the Item class above will create the following:

@Override
public String toString() {
 return "Item [i=" + i + ", j=" + j + ", k=" + k + ", l=" + l + ", m="
   + m + ", n=" + n + ", o=" + o + "]";
}

Using reflection would allow a programmatic way to observe one's own fields, but reflection itself is a fairly expensive (read: slow) process, so unless it is truly required, using a fixed toStringmethod written at runtime is probably going to be more desirable.

coobird
Actually the only reason I was thinking of Reflection is because I need to write a toString and a toJSON representation. I can write them manually but sometimes the classes represent items from a database so they could have a lot of fields. Would you still suggest a manual approach?
Legend
It's really going to be up to the performance requirements of the toString and toJSON methods. If performance is not a concern, then using reflection probably isn't going to matter. If performance is a concern, it's probably going to be a good idea to profile the "manually" written code and code written using reflection, and see if the difference is going to matter.
coobird
Understood. Thanks for the explanation. I think I will need a performance. I am writing a REST web service so I guess the answer is pretty clear from your explanation.
Legend
As always, when it comes to performance, it would be prudent to profile some code to be sure -- don't just take other people's words ;)
coobird
:) Thank You for that. I will go with the eclipse generation method for the toString representation and a reflection for toJSON and will profile it.
Legend
+7  A: 

You could do:

@Override
public String toString() {
  StringBuilder sb = new StringBuilder();
  sb.append(getClass().getName());
  sb.append(": ");
  for (Field f : getClass().getDeclaredFields()) {
    sb.append(f.getName());
    sb.append("=");
    sb.append(f.get(this));
    sb.append(", ");
  }
  return sb.toString();
}

Don't use string concatenation to construct an end result from 15 data members, particularly if the toString() will be called a lot. The memory fragmentation and overhead could be really high. Use StringBuilder for constructing large dynamic strings.

I usually get my IDE (IntelliJ) to simply generate toString() methods for me rather than using reflection for this.

Another interesting approach is to use the @ToString annotation from Project Lombok:

import lombok.ToString;

@ToString(excludes="id")
public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;

  @ToString(callSuper=true, includeFieldNames=true)
  public static class Square extends Shape {
    private final int width, height;

    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

I find this much more preferable to, say, Jakarta Commons toString builders because this approach is far more configurable and it's also built at compile-time not run-time.

cletus
Thank you for this. I would go with the first approach depending on the profiling results because the second approach uses one more library. I might have to write the toJSON for just three to four objects so I'll leave it to the profiling to decide.
Legend
+1 for compile time Lombok example
Thorbjørn Ravn Andersen
For Lombok, you only need a library during compilation. As long as you don't need @SneakyThrows, thare are no runtime-dependencies in Lombok
Roel Spilker
+1  A: 

You may run into the problem of speed. If you are using reflection, it can be really slow, in comparison to running native code. If you are going to use reflection, then you should probably have an in memory cache of the variables you are goaing to iterate over.

Milhous
+1  A: 

There is such an api, and it is called Java Reflection

To accomplish what you are requesting, you can simply do something like:

 Class<?> cls = this.getClass();
 Field fieldlist[] = cls.getDeclaredFields();
 for (Field aFieldlist : fieldlist) {
   // build toString output with StringBuilder()
 }
Steve Levine
Thank You for that.
Legend
+1  A: 

This should be exactly what you are looking for

    public String toString() {
 StringBuilder sb = new StringBuilder();
 try {
  Class c = Class.forName(this.getClass().getName());
  Method m[] = c.getDeclaredMethods();
  Object oo;

  for (int i = 0; i < m.length; i++)
   if (m[i].getName().startsWith("get")) {
    oo = m[i].invoke(this, null);
    sb.append(m[i].getName().substring(3) + ":"
      + String.valueOf(oo) + "\n");
   }
 } catch (Throwable e) {
  System.err.println(e);
 }
 return sb.toString();
}
medopal