Converting Objects to Strings

- 110 - magnitude--; return magnitude; } } The conversion times compared to the JDK conversions are shown in Table 5-4 . As with float s, formatting double s with the JDK conversion requires additional steps and would consequently take longer, but the method shown here takes even less time, as you normally print fewer digits that require fewer loop iterations. Table 5-4, Time Taken to Append a double to a StringBuffer VM 1.2 1.3 HotSpot 1.0 1.1.6 JDK double conversion 100 92 129 134 Optimized double conversion 16 16 32 23

5.3.6 Converting Objects to Strings

Converting Object s to String s is also inefficient in the JDK. For a generic object, the toString method is usually implemented by calling any embedded objects toString method, then combining the embedded strings in some way. For example, Vector.toString calls toString on all its elements, and combines the generated substrings with the comma character surrounded by opening and closing square brackets. Although this conversion is generic, it usually creates a huge number of unnecessary temporary objects. If the JDK had taken the printOn: aStream paradigm from Smalltalk , the temporary objects used would be significantly reduced. This paradigm basically allows any object to be appended to a stream. In Java, it looks something like: public String toString { StringBuffer s =new StringBuffer ; appendTos; return s.toString ; } public void appendToStringBuffer s { The real work of converting to strings. Any embedded objects would have their appendTo methods called, NOT their toString methods. ... } This implementation allows far fewer objects to be created in converting to strings. In addition, as StringBuffer is not a stream, this implementation becomes much more useful if you use a java.io.StringWriter and change the appendTo method to accept any Writer , for example: public String toString { java.io.StringWriter s =new java.io.StringWriter ; appendTos; return s.getBuffer .toString ; } public void appendTojava.io.Writer s { - 111 - The real work of converting to strings. Any embedded objects would have their appendTo methods called, NOT their toString methods. ... } This implementation allows the one appendTo method to write out any object to any streamed writer object. Unfortunately, this implementation is not supported by the Object class, so you need to create your own framework of methods and interfaces to support this implementation. I find that I can use an Appendable interface with an appendTo method, and then write toString methods that check for that interface: public interface Appendable { public void appendTojava.io.Writer s; } public class SomeClass implements Appendable { Object[] embeddedObjects; ... public String toString { java.io.StringWriter s =new java.io.StringWriter ; appendTos; return s.getBuffer .toString ; } public void appendTojava.io.Writer s { The real work of converting to strings. Any embedded objects would have their appendTo methods called, NOT their toString methods. for int i = 0; iembeddedObjects.length; i++ if embeddedObjects[i] instanceof Appendable Appendable embeddedObjects[i].appendTos; else s.writeembeddedObjects[i].toString ; } } In addition, you can extend this framework even further to override the appending of frequently used classes such as Vector , allowing a more efficient conversion mechanism that uses fewer temporary objects: public class AppenderHelper { final static String NULL = null; final static String OPEN = [; final static String CLOSE = ]; final static String MIDDLE = , ; public void appendCheckingAppendableObject o, java.io.Writer s { Use more efficient Appendable interface if possible, and NULL string if appropriate if o = v.elementAt0 == null s.writeNULL; else if o instanceof Appendable - 112 - Appendable o.appendTos; else s.writeo.toString ; } public void appendVectorjava.util.Vector v, java.io.Writer s { int size = v.size ; Object o; Write the opening bracket s.writeOPEN; if size = 0 { Add the first element appendCheckingAppendablev.elementAt0, s; And add in each other element preceded by the MIDDLE separator forint i = 1; i size; i++; { s.appendMIDDLE; appendCheckingAppendablev.elementAti, s; } } Write the closing bracket s.appendCLOSE; } } If you add this framework to an application, you can support the notion of converting objects to string representations to a particular depth. For example, a Vector containing another Vector to depth two looks like this: [1, 2, [3, 4, 5]] But to depth one, it looks like this: [1, 2, Vector4444] The default Object.toString implementation in the JDK writes out strings for objects as: return getClass .getName + + Integer.toHexStringhashCode ; The JDK implementation is inefficient for two reasons. First, the method creates an unnecessary intermediate string because it uses the concatenation operator twice. Second, the Class.getName method which is a native method also creates a new string every time it is called: the class name is not cached. It turns out that if you reimplement this to cache the class name and avoid the extra temporary strings, your conversion is faster and uses fewer temporary objects. The two are related, of course: using fewer temporary objects means less object-creation overhead. You can create a generic framework that converts the basic data types, while also supporting the efficient conversion of JDK classes such as Vector , as well as Integer , Long , etc.. With this framework in place, I find that performance is generally improved because the application uses more efficient conversion algorithms and reduces the number of temporary objects. In almost every respect, this framework is better than the simpler framework, which supports only the toString method. - 113 -

5.4 Strings Versus char Arrays