Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

JavaWorld Daily Brew

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);
        }
    }
</pre>

<p>
The TSDemo1 program defines a class <code>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.