Java Tip 99: Automate toString() creation
Exploit the power of Reflection and save significant coding time
Developers working on large projects commonly spend hours writing useful toString
methods. Even if each class doesn’t get its own toString
method, each data container class will. Allowing each developer to write toString
his or her own way can lead to chaos; each developer will undoubtedly come up with a unique format. As a result, using the output during debugging becomes more difficult than necessary with no obvious benefit. Therefore, each project should standardize on a single format for toString
methods and then automate their creation.
Automate toString
I will now demonstrate a utility with which you can do just that. This tool automatically generates a regular and robust
toString
method for a specified class, almost eliminating the time spent developing the method. It also centralizes the
toString()
format. If you change the format, you must regenerate the
toString
methods; however, this is still much easier than manually changing hundreds or thousands of classes.
Maintaining the generated code is easy, too. If you add more attributes in the classes, you may be required to make the changes in the toString
method also. Since the generation of toString
methods is automated, you need only run the utility on the class again to make your changes. This is simpler and less error-prone than the manual approach.
The code
This article is not meant to explain the Reflection API; the following code assumes that you have at least an understanding of the concepts behind Reflection. You can visit the
section for the Reflection API’s documentation. The utility is written as follows:
package fareed.publications.utilities;
import java.lang.reflect.*;
public class ToStringGenerator
{
public static void main(String[] args)
{
if (args.length == 0)
{
System.out.println("Provide the class name as the command line argument");
System.exit(0);
}
try {
Class targetClass = Class.forName(args[0]);
if (!targetClass.isPrimitive() && targetClass != String.class)
{
Field fields[] = targetClass.getDeclaredFields();
Class cSuper = targetClass.getSuperclass(); // Retrieving the super class
output("StringBuffer buffer = new StringBuffer(500);"); // Buffer Construction
if (cSuper != null && cSuper != Object.class) {
output("buffer.append(super.toString());"); // Super class's toString()
}
for (int j = 0; j < fields.length; j++) {
output("buffer.append("" + fields[j].getName() + " = ");"); // Append Field name
if (fields[j].getType().isPrimitive() || fields[j].getType() == String.class) // Check for a primitive or string
output("buffer.append(this." + fields[j].getName() + ");"); // Append the primitive field value
else
{
/* It is NOT a primitive field so this requires a check for the NULL value for the aggregated object */
output("if ( this." + fields[j].getName() + "!= null )" );
output("buffer.append(this." + fields[j].getName() + ".toString());");
output("else buffer.append("value is null"); ");
} // end of else
} // end of for loop
output("return buffer.toString();");
}
} catch (ClassNotFoundException e) {
System.out.println("Class not found in the class path");
System.exit(0);
}
}
private static void output(String data)
{
System.out.println(data);
}
}
The code output channel
The format of the code also depends on your project tool requirements. Some developers may prefer to have the code in a user-defined file on disk. Other developers are satisfied with the
system.out
console, which allows them to copy and embed the code in the actual file manually. I simply leave those options to you and use the simplest method:
system.out
statements.
Limitations to the approach
There are two important limitations to this approach. The first is that it doesn’t support objects containing cycles. If object A contains a reference to object B, which then contains a reference to object A, this tool won’t work. However, that case will be rare for many projects.
The second limitation is that adding or subtracting member variables requires regeneration of the toString
method. Since this needs to be done with or without the tool, it isn’t a problem specific to this approach.
Conclusion
In this article, I’ve explained a small automation utility that can really improve developer productivity and play a small but important role in the reduction of overall project timelines.
Follow-up tips
After this tip was published, I received a few suggestions from readers on how to improve the code. In this follow up, I explain how I’ve updated the utility based on those suggestions and my own insights. You can find the source code for these improvements in Resources.
Improvement #1, suggested by Sangeeta Varma
In my original code, I did not handle the array types for the object and primitive data type; the new code now handles the array data. However, the code only goes up to single dimension arrays and will not work for multiple dimension arrays. I haven’t been able to come up with a generic solution for this problem since, to the best of my knowledge, there is no restriction on the number of dimensions for data types in Java (the only restriction is the available memory). I welcome any feedback you can offer for a solution.
Improvement #2, suggested by Chris Sanscraint
Originally I proposed the utility for development time and not for the runtime environment. Allowing the utility to run at runtime can be very handy, but may take a few more CPU cycles. However, the object dumping/debugging (basic use of toString()
) is usually done during the development time, and is switched off for the production environment. In some cases, this switching off in the production environment may not be applicable as some projects may use toString()
for business logic purposes. I suggest making that decision on a project-by-project basis.
Before developing this utility, I already had this runtime flexibility in my mind. First, I developed a separate delegating class that was used by any client class to generate the toString()
. The class generated it using a method call like return ToStringGenerator.generateToString(this)
, where this
points to the current instance of the client class and the code statement is written in the toString()
method implementation. But that approach failed because the Reflection API does not have the ability to get the values for the private members at runtime. So the class was only useful for public members, which I didn’t want.
But then Mr. Sanscraint pointed out that the same Reflection API code gets the value of the private members at runtime when the code is written within a method of the same caller class. So I have updated the utility to be used at runtime, and in addition, the toString()
method will never need to be updated or edited for the subtraction or addition of any attributes in the target class.
Improvement #3, suggested by Eric Ye
Originally I used the this
prefix for the member variables access in the generated code, but Mr. Ye pointed out that the code may also be used in a static method or even to output static members. So the updated code can now handle both class and instance members. Mr. Ye also identified a bug, which has been fixed in this version, that caused the class to generate useless code for attributeless classes.
Code modifications
After making the utility runtime-enabled, I was frustrated by having to copy/paste the methods in each class, which became difficult since the new code was comprised of multiple methods.
One solution would be to create an interface/abstract base class that would at least solve the problem of method signatures, but copy/paste would still be required. The abstract base class solution would also restrict the client from deriving from another class.
An inner class, however, has the ability to access the private members of the parent class so the reflection code, running within its methods, could also get the private values. So I decided to change the utility into an inner class that could be inserted into any parent client class. I have also provided ToStringGeneratorExample.java that uses the ToStringGenerator.java as the inner class to implement the toString()
method.
Finally, I want to thank those people who offered their suggestions on improving this approach.