Wednesday, October 17, 2007

Flex: ObjectUtil.toString() Versus Inherited Object's toString()

When using trace, Alert.show, or so other method for logging in Flex applications, it is often convenient to print out the representation of ActionScript objects. This blog entry compares two common methods for printing out a Flex object's representation.

ActionScript 3.0 (part of Flex 2) provides an "all static class" called ObjectUtil that provides a number of enormously helpful static functions. These useful functions include introspection; deep object copying; comparing objects, strings, and numbers; and printing out "pretty" string representations of objects (the focus of this blog entry).

The second approach to obtaining a textual representation of an ActionScript object is to use the Java-like toString() method all ActionScript objects can override from the Object class. As with Java, these individual toString() methods are most useful when called on instances of classes for which the toString() method has been specifically overridden for that class. In fact, in ActionScript, a descendant class must override toString() if it is to be called explicitly on that class.

As a side note, the ActionScript Object.toString() does not work exactly like Java's Object.toString(). For example, if a toString() method is not explicitly defined on a child ActionScript class, no toString() function can be called directly and explicitly on that class. Attempting to do so results in an error message: "Error: Call to a possibly undefined method toString through a reference with static type ..." In Java, if a toString() method is not explicitly defined, it can still be called because an inherited version can be called, even if it is on the root class Object. However, even in ActionScript, implicit uses of toString on an object are allowable even when no toString() has been formally defined for an ActionScript object. For example, if I commented out the toString() function in the Person.as code shown below and had declared a Person object variable named aPerson, then trace(aPerson) would be allowable, but trace(aPerson.toString()) would not be allowable.

It is worth noting that while most ActionScript classes must use the "override" keyword to explicitly override a parent class' method, this is not the case for the static functions defined in the Object class. Instead, these functions are overridden in each first descendant class using a simple redefinition of the function with the same signature. The code example below (Person.as) shows how toString() can be overridden (note without the override keyword) by a descendant class.

So, why would one use ObjectUtil.toString() rather than the toString() method that all ActionScript classes have access to (whether their own overridden version or a version inherited from an ancestor class)?

One reason for preference of ObjectUtil.toString() might be because no useful toString() method has been implemented for a specific class of interest. As described above, toString() cannot be invoked on an ActionScript object unless that class has explicitly overridden toString().

Another reason to prefer ObjectUtil.toString() might be that the the toString() method implemented for a specific class may lack the information provided by the ObjectUtil.toString() method on that same class.

A third situation in which ObjectUtil.toString() may be more useful than individual class toString() methods is the case where there are many nested classes (has-a relationships) and some of the constituent parts do not have implemented toString() methods or the outer/composite class' toString() method does not make use of composing class' toString() methods.

The following relatively simple ActionScript class works as an example of some of the differences in output using mx.utils.ObjectUtil.toString() as compared to using toString as defined on objects themselves. This example actually puts the "best foot forward" for individual object toString() implementations because the objects used in this example do have relevant implementations of toString(). If these objects did not have their own relevant toString() implementations, the output information from the objects using their own toString() implementations would be nowhere near as useful as ObjectUtil.toString()'s output for those objects.

Main MXML Application File: ToStringTester.mxml


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="250" height="250"
applicationComplete="runTests(event)">

<mx:Script>
<![CDATA[
/*
* To compile this application from the command line, use
* mxmlc -compiler.debug=true ToStringTester.mxml
*
* This needs to be compiled with debug on and a debug
* Flash player needs to be used when loading application
* so that trace calls will be recorded.
*/

import mx.collections.ArrayCollection;
import mx.utils.ObjectUtil;
import example.tostring.Person;

public function runTests(aEvent:Event):void
{
printHeader("OBJECT");
const person1:Person = buildInstanceOfPerson("Dillon", "Frank", 19);
printObjectWithStaticToString(person1);
printObjectWithItsOwnToString(person1);

printHeader("SIMPLE STRING ARRAY");
const simpleArray:Array = new Array( "blue", "green", "red", "yellow" );
printArrayWithStaticToString( simpleArray );
printArrayWithItsOwnToString( simpleArray );

printHeader("OBJECT ARRAY");
const person2:Person = buildInstanceOfPerson("Smith", "Wesley", 35);
const personArray:Array = new Array( person1, person2 );
printArrayWithStaticToString( personArray );
printArrayWithItsOwnToString( personArray );

printHeader("ARRAY COLLECTION");
const personArrayCollection:ArrayCollection =
new ArrayCollection( personArray );
printArrayCollectionWithStaticToString( personArrayCollection );
printArrayCollectionWithItsOwnToString( personArrayCollection );

printHeader("XMLLIST");
const xmlPeople:XMLList = buildPeopleXml().Person;
printXMLListWithStaticToString( xmlPeople );
printXMLListWithItsOwnToString( xmlPeople );
printXMLListWithItsOwnToXMLString( xmlPeople );
}

public function printObjectWithStaticToString(aPerson:Person):void
{
trace( "--- Display object using static ObjectUtil.toString():" );
trace( ObjectUtil.toString(aPerson) );
}

public function printObjectWithItsOwnToString(aPerson:Person):void
{
trace( "--- Display object using its own toString(): " );
trace( aPerson ); // using implicit call to toString()
}

public function printArrayWithStaticToString(aArray:Array):void
{
trace( "--- Display array using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aArray) );
}

public function printArrayWithItsOwnToString(aArray:Array):void
{
trace( "--- Display array using its own toString(): " );
trace( aArray.toString() );
}

public function printArrayCollectionWithStaticToString(
aArray:ArrayCollection):void
{
trace( "--- Display array collection using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aArray) );
}

public function printArrayCollectionWithItsOwnToString(
aArray:ArrayCollection):void
{
trace( "--- Display array collection using its own toString(): " );
trace( aArray.toString() );
}

public function printXMLListWithStaticToString(aXml:XMLList):void
{
trace( "--- Display XMLList using static ObjectUtil.toString(): " );
trace( ObjectUtil.toString(aXml) );
}

public function printXMLListWithItsOwnToString(aXml:XMLList):void
{
trace( "--- Display XMLList using its own toString(): " );
trace( aXml.toString() );
}

public function printXMLListWithItsOwnToXMLString(aXml:XMLList):void
{
trace( "--- Display XMLList using its own toXMLString(): " );
trace( aXml.toXMLString() );
}

public function printHeader(aHeader:String=""):void
{
trace("------------------------------------");
trace(" " + aHeader );
trace("------------------------------------");
}

public function printBreaker():void
{
trace("------------------------------------");
}

public function buildInstanceOfPerson( aLastName:String,
aFirstName:String,
aAge:uint ):Person
{
var person:Person = new Person();
person.setLastName(aLastName);
person.setFirstName(aFirstName);
person.setAge(aAge);
return person;
}

public function buildPeopleXml():XML
{
var person:XML = <People>
<Person lastName="Johnson" firstName="Jim" age="45" />
<Person lastName="Smith" firstName="John" age="50" />
<Person lastName="Rhodes" firstName="Julie" age="75" />
</People>;
return person;
}
]]>
</mx:Script>

<mx:VBox id="mainBox">
<mx:Label text="See trace() Output" />
</mx:VBox>

</mx:Application>



In the above application, the ActionScript class example.tostring.Person is referenced and used. This code for this user-defined class is shown next.


Class Used in toString Examples: example.tostring.Person



package example.tostring
{
public class Person
{
private var lastName:String;
private var firstName:String;
private var age:uint;

public function Person()
{
}

public function getLastName():String
{
return lastName;
}
public function setLastName(aLastName:String):void
{
lastName = aLastName;
}

public function getFirstName():String
{
return firstName;
}
public function setFirstName(aFirstName:String):void
{
firstName = aFirstName;
}

public function getAge():uint
{
return age;
}
public function setAge(aAge:uint):void
{
age = aAge;
}

/**
* Return String representation of me.
*
* @return String representation of me.
*/
public function toString():String
{
return "Last Name: " + lastName + "; "
+ "First Name: " + firstName + "; "
+ "Age: " + age;
}
}
}



After this simple application (MXML file and the ActionScript Person.as file) has been compiled into ToStringTester.swf, it should be run in a debug Flash Player with the debugger turned on. When this is done, the trace() output will be displayed in the debugger window and will look something like that shown in the screen snapshot below.




As the trace output indicates, ObjectUtil.toString() can be useful for providing additional information above and beyond that provided by an object's toString(). Had toString() not been implemented for the Person class, the advantage of ObjectUtil.toString() would have been even more apparent.

For the string representation of individual objects, ObjectUtil.toString() was not much more useful in this particular case. However, the array representation provided by ObjectUtil.toString() was superior to the individual array's toString() because it provided the array index numbering in square brackets before each array element. Interestingly, the XMLList representation is identical for the two approaches and even for the third approach of using the XMLList-specific toXMLString() method.

ObjectUtil.toString() provides a global, static function that allows for useful representation of ActionScript objects to be obtained even when the implementor of the ActionScript object has not implemented that object's toString() method. This is especially useful for complex objects that consist of many composing parts such as other objects, arrays, and other data structures.

Related Blogs and Articles

No comments: