Writing toString Methods & Tech Days

by Glen McCluskey

One of the standard methods defined in java.lang.Object is toString. This method is used to obtain a string representation of an object. You can (and normally should) override this method for classes that you write. This tip examines some of the issues around using toString.

Let's first consider some sample code:

    class MyPoint {
        private final int x, y;
    
        public MyPoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
   
    public class TSDemo1 {
        public static void main(String args[]) {
            MyPoint mp = new MyPoint(37, 47);
    
            // use default Object.toString()
    
            System.out.println(mp);
    
            // same as previous, showing the
            // function of the default toString()
    
            System.out.println(mp.getClass().getName() 
                + "@" 
                + Integer.toHexString(mp.hashCode()));
    
            // implicitly call toString() on object
            // as part of string concatenation
    
            String s = mp + " testing";
            System.out.println(s);
    
            // same as previous, except object
            // reference is null
    
            mp = null;
            s = mp + " testing";
            System.out.println(s);
        }
    }

The TSDemo1 program defines a class MyPoint to represent X,Y points. It does not define a toString method for the class. The program creates an instance of the class and then prints it. When you run TSDemo1, you should see a result that looks something like this:

    MyPoint@111f71
    MyPoint@111f71
    MyPoint@111f71 testing
    null testing

You might wonder how it's possible to print an arbitrary class object. The library methods such as System.out.println know nothing about the MyPoint class or its objects. So how is it possible to convert such an object to string form and then print it, as the first output statement in TSDemo1 does?

The answer is that println calls the java.io.PrintStream.print(Object) method, which then calls the String.valueOf method. The String.valueOf method is very simple:

    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

When println is called with a MyPoint object reference, the String.valueOf method converts the object to a string. String.valueOf first checks to make sure that the reference is not null. It then calls the toString method for the object. Since the MyPoint class has no toString method, the default one in java.lang.Object is used instead.

What does the default toString method actually return as a string value? The format is illustrated in the second print statement above. The name of the class, an "@", and the hex version of the object's hashcode are concatenated into a string and returned. The default hashCode method in Object is typically implemented by converting the memory address of the object into an integer. So your results might vary from those shown above.

The third and fourth parts of the TSDemo1 example illustrate a related idea: when you use "+" to concatenate a string to an object, toString is called to convert the object to a string form. You need to look at the bytecode expansion for TSDemo1 to see that. You can look at the bytecode for TSDemo1 (that is, in a human-readable form) by issuing the javap command as follows:

javap -c . TSDemo1

If you look at the bytecode, you'll notice that part of it involves creating a StringBuffer object, and then using StringBuffer.append(Object) to append the mp object to it. StringBuffer.append(Object) is implemented very simply:

    public synchronized StringBuffer append(Object obj) {
        return append(String.valueOf(obj));
    }

As mentioned earlier, String.valueOf calls toString on the object to get its string value.

O.K., so much for invoking the default toString method. How do you write your own toString methods? It's really very simple. Here's an example:

    class MyPoint {
        private final int x, y;
    
        public MyPoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public String toString() {
            return x + " " + y;
        }
    }
    
    public class TSDemo2 {
        public static void main(String args[]) {
            MyPoint mp = new MyPoint(37, 47);
    
            // call MyPoint.toString()
    
            System.out.println(mp);
    
            // call toString() and
            // extract the X value from it
    
            String s = mp.toString();
            String t = s.substring(0, s.indexOf(' '));
            int x = Integer.parseInt(t);
            System.out.println(t);
        }
    }

When you run the TSDemo2 program, the output is: 37 47 37

The toString method in this example does indeed work, but there are a couple of problems with it. One is that there is no descriptive text displayed in the toString output. All you see is a cryptic "37 47". The other problem is that the X,Y values in MyPoint objects are private. There is no other way to get at them except by picking apart the string returned from toString. The second part of the TSDemo2 example shows the code required to extract the X value from the string. Doing it this way is error-prone and inefficient.

Here's another approach to writing a toString method, one that clears up the problems in the previous example:

    class MyPoint {
        private final int x, y;
    
        public MyPoint(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        public String toString() {
            return "X=" + x + " " + "Y=" + y;
        }
    
        public int getX() {
            return x;
        }
    
        public int getY() {
            return y;
        }
    }
   
    public class TSDemo3 {
        public static void main(String args[]) {
            MyPoint mp = new MyPoint(37, 47);
    
            // call MyPoint.toString()
    
            System.out.println(mp);
    
            // get X,Y values via accessor methods
    
            int x = mp.getX();
            int y = mp.getY();
            System.out.println(x);
            System.out.println(y);
        }
    }

The output is:

    X=37 Y=47
    37
    47

This example adds some descriptive text to the output format, and defines a couple of accessor methods to get at the X,Y values. In general, when you write a toString method, the format of the string that is returned should cover all of the object contents. Your toString method should also contain descriptive labels for each field. And there should be a way to get at the object field values without having to pick apart the string. Note that using "+" within toString to build up the return value is not necessarily the most efficient approach. You might want to use StringBuffer instead.

Primitive types in the Java programming language, such as int, also have toString methods, for example Integer.toString(int). What about arrays? How can you convert an array to a string? You can assign an array reference to an Object reference, but arrays are not really classes. However, it is possible to use reflection to implement a toString method for arrays. The code looks like this:

    import java.lang.reflect.\*;
    
    public class TSDemo4 {
        public static String toString(Object arr) {
    
            // if object reference is null or not
            // an array, call String.valueOf()
    
            if (arr == null || 
                       !arr.getClass().isArray()) {
                return String.valueOf(arr);
            }
    
            // set up a string buffer and
            // get length of array
    
            StringBuffer sb = new StringBuffer();
            int len = Array.getLength(arr);
    
            sb.append('[');
    
            // iterate across array elements
    
            for (int i = 0; i < len; i++) {
                if (i > 0) {
                    sb.append(',');
                }
    
                // get the i-th element
    
                Object obj = Array.get(arr, i);
    
                // convert it to a string by
                // recursive toString() call
    
                sb.append(toString(obj));
            }
            sb.append(']');
    
            return sb.toString();
        }
    
        public static void main(String args[]) {

            // example #1

            System.out.println(toString("testing"));
    
            // example #2

            System.out.println(toString(null));
    
            // example #3

            int arr3[] = new int[]{
                1,
                2,
                3
            };
            System.out.println(toString(arr3));
    
            // example #4

            long arr4[][] = new long[][]{
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9}
            };
            System.out.println(toString(arr4));
    
            // example #5

            double arr5[] = new double[0];
            System.out.println(toString(arr5));
    
            // example #6

            String arr6[] = new String[]{
                "testing",
                null,
                "123"
            };
            System.out.println(toString(arr6));
    
            // example #7

            Object arr7[] = new Object[]{
                new Object[]{null, new Object(), null},
                new int[]{1, 2, 3},
                null
            };
            System.out.println(toString(arr7));
        }
    }

The TSDemo4 program creates a toString method, and then passes the toString method an arbitrary Object reference. If the reference is null or does not refer to an array, the program calls the String.valueOf method. Otherwise, the Object refers to an array. In that case, TSDemo4 uses reflection to access the array elements. Array.getLength and Array.get are the key methods that operate on the array. After an element is retrieved, the program calls toString recursively to obtain the string for the element. Doing it this way ensures that multidimensional arrays are handled properly.

The output of the TSDemo4 program is:

    testing
    null
    [1,2,3]
    [[1,2,3],[4,5,6],[7,8,9]]
    []
    [testing,null,123]
    [[null,java.lang.Object@111f71,null],[1,2,3],null]
Obviously, if you have a huge array, and you call toString, it will use a lot of memory, and the resulting string might not be particularly useful or readable by a human.

For more information about using toString methods, see Section 2.6.2, Method Invocations, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/. Also see item 9, Always override toString, in "Effective Java Programming Language Guide" by Joshua Bloch (http://java.sun.com/docs/books/effective/).

\*\*\*\*\*\*\*\*\*\*

Sun Tech Days

The Sun Tech Days program educates developers on many technologies in a two-day format, and includes hands-on labs, university training, community programs and technical sessions. Attend an upcoming free session:

Taipei, Taiwan Oct. 19
Shanghai, China Oct. 23-25
Beijing, China Nov. 1-3
Tokyo, Japan Nov. 6-8
Frankfurt, Germany Dec. 3-5

See the Sun Tech Days website for more information about a Tech Days near you.

Comments:

the point with toString() method is addressing inheritance and composition/aggregation

about inheritance, in my classes i tend to "deprecate" toString method (implemented as "final" in base classes), and redirect to a dalegate formatter tha let to extend "stringfied" object rapresentantion, goin down thru the inheritance derivation chain.

As a sample:

public String toString() {
return format();
}

public final String format() {
return format(FMT_DEFAULT_OPTIONS);
}

public final String format(long formatOptions) {
DumpWriter s = new DumpWriter(formatOptions);
format(s);
return s.toString();
}

public final DumpWriter format(DumpWriter s) {
s.beginDump();
toString(s);
s.endDump();
return s;
}

protected void toString(DumpWriter s) {
s.startDump(this);
}

with a DumpWriter utility class that hold a stringbuilder buffer, and many utility "print" methods, with signature
print(propertyName, propertyValue)

In any case, for lazy writers, reflection is an option:

@see org.apache.commons.lang.builder.ReflectionToStringBuilder

Posted by Giovanni Pelosi on October 09, 2007 at 06:39 PM PDT #

Guys, why invent a new bicycle if we already have one: Apache Commons Lang, org.apache.commons.lang.builder.ToStringBuilder. It is smart tool and cover most of developer's needs.

See Javadoc: http://commons.apache.org/lang/api-release/index.html

Posted by Viacheslav Garmash on October 10, 2007 at 12:45 AM PDT #

Agree totally with using Commons implementation, however this is a good insight for some beginners.

As another note, shouldn't we be recommending the use of StringBuilder instead of StringBuffer if thread safety isn't an issue (most of the time)?

Posted by Matt B on October 10, 2007 at 05:57 PM PDT #

Since Java6, local StringBuffer objects that don't escape, are as fast as StringBuilders.

Posted by Roel Spilker on October 10, 2007 at 06:43 PM PDT #

Arrays.toString.

Posted by Keith Wansbrough on October 12, 2007 at 07:16 AM PDT #

Don't care about things you're not certain of. To explicitly use StringBuilder keeps the VM from optimizing in future releases. Optimization is up to the VM and the common pattern for toString-calls is just concatenation. Common patterns will be optimized (native compilation, reorder, unravel, inline...), while special one's won't. And if you often call toString you should think about changing your design while released software shouldn't always print out all objects and their attributes all the time. (Short form: don't waste 80% of time to save 2% performance.)

Posted by Onkobu Tanaake on November 01, 2007 at 08:47 PM PDT #

Roel, I was interested in your comment about StringBuffer being as fast as StringBuilder as of Java6. I see the API docs still recommend using StringBuilder "as it supports all of the same operations but it is faster": http://java.sun.com/javase/6/docs/api/java/lang/StringBuffer.html

I'd be interested in reading about the improvements you refer to in StringBuffer. Can you please provide a link.

Posted by Matt B on November 14, 2007 at 08:48 PM PST #

You can have a look at an article by Brian Goetz. http://www.ibm.com/developerworks/java/library/j-jtp10185/index.html

It could be that it is only true when running in server mode using -XX:+DoEscapeAnalysis.

Posted by Roel Spilker on November 28, 2007 at 09:10 PM PST #

Post a Comment:
Comments are closed for this entry.
About

John O'Conner

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today