Saturday, April 18, 2009

The Value of String.valueOf

Most Java developers have probably had their fill of NullPointerException. Most of us have learned the value of doing certain things to reduce our "opportunities" of encountering the NullPointerException. Indeed, there is a Wiki page dedicated to preventing or reducing NullPointerExceptions.

Several people have argued for additional language support for improved and easier handling of potential null. These include Java SE 7 proposals, Optimized Null Check, and Kinga Dobolyi's thesis Changing Java’s Semantics for Handling Null Pointer Exceptions.

Among the many things we can already do rather easily to reduce our encounters with NullPointerException, one particular easy thing to do is to apply String.valueOf(Object) when appropriate. The String.valueOf(Object) method, as its Javadoc-generated documentation states, returns "null" if the passed in object is null and returns the results on the passed-in Object's toString() call if the passed-in Object is not null. In other words, String.valueOf(String) does the null checking for you.

The use of String.valueOf(Object) is particularly useful when implementing toString methods on custom classes. Because most toString implementations provide the class's data members in String format, String.valueOf(Object) is a natural fit. All Java objects based on classes that extend Object provide a toString() implementation even if it is simply their parent's (or even Object's) implementation of toString(). However, if a member class implements toString but the member itself is null rather than an instance of the class, then the toString() does no good (and actually leads to a NullPointerException when called).

This is demonstrated with the following example code.

StringHandlingExample.java


package dustin.examples;

import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;

/**
* Example class demonstrating use of String representations available through
* implicit String, toString(), and String.valueOf().
*/
public class StringHandlingExample
{
private static final String NEW_LINE = System.getProperty("line.separator");

/** Using java.util.logging. */
private static Logger LOGGER = Logger.getLogger(
StringHandlingExample.class.getName());

/**
* Main function for running tests/demonstrations.
*
* @param arguments Command-line arguments; none anticipated.
*/
public static void main(final String[] arguments)
{
printHeader("String representation of direct Strings", System.out);
final PersonName personName = new PersonName("Flintstone", null);
System.out.println("Person's Name [DIRECT]: " + personName);
System.out.println("Person's Name [TOSTRING]: " + personName.toString());
System.out.println("Person's Name [STRING.VALUEOF]: " + String.valueOf(personName));
printBlankLine(System.out);

printHeader("String representation of non-null complex object", System.out);
final Person personOne = new Person(personName);
System.out.println("Person One [DIRECT]: " + personOne);
System.out.println("Person One [TOSTRING]: " + personOne.toString());
System.out.println("Person One [STRING.VALUEOF]: " + String.valueOf(personOne));
printBlankLine(System.out);

printHeader("String representation of null complex object", System.out);
final Person personTwo = new Person(null);
System.out.println("Person Two [DIRECT]: " + personTwo);
System.out.println("Person Two [TOSTRING]: " + personTwo.toString());
System.out.println("Person Two [STRING.VALUEOF]: " + String.valueOf(personTwo));
printBlankLine(System.out);
}

public static void printHeader(final String message, final OutputStream out)
{
final String headerSeparator =
"====================================================================";

try
{
out.write((headerSeparator + NEW_LINE + message + NEW_LINE).getBytes());
out.write((headerSeparator + NEW_LINE).getBytes());
}
catch (IOException ioEx)
{
System.out.println(headerSeparator);
System.out.println(message);
System.out.println(headerSeparator);
LOGGER.warning("Could not write header information to provided OutputStream.");
}
}

public static void printBlankLine(final OutputStream out)
{
try
{
out.write(NEW_LINE.getBytes());
}
catch (IOException ioEx)
{
System.out.println(NEW_LINE);
LOGGER.warning("Could not write blank line to provided OutputStream.");
}
}

/**
* Class upon which to call toString.
*/
private static class PersonName
{
private String lastName;
private String firstName;

public PersonName(final String newLastName, final String newFirstName)
{
lastName = newLastName;
firstName = newFirstName;
}

/**
* Provide String representation of me.
*
* @return My String representation.
*/
@Override
public String toString()
{
return firstName + " " + lastName;
}
}

private static class Person
{
private PersonName name;

public Person(final PersonName newName)
{
name = newName;
}

/**
* Provide String representation of me.
*
* @return My String representation.
*/
public String toString()
{
// Don't use -- leads to compiler time error (incompatible types)
//return name;

// Don't use -- can lead to runtime error (NullPointerException)
//return name.toString();

// It's all good
return String.valueOf(name);
}
}
}


The above code can be used to demonstrate building of a toString method on a complex object and how its behaves when called by an owning class. The method of most interest is at the bottom of the code shown above. Two return values are commented out because of problems associated with them. The final example, using String.valueOf(Object) is NOT commented out because it works the best each time it is run whether or not the complex PersonName object is null. The next three images show the output for each of these presentations of the Person objects' String representations.

String Value from Complex Object - Compile-time Error




String Value from Complex Object toString() - Potential Runtime NullPointerException



String Value from Complex Object String.valueOf() - Nulls Handled Gracefully




Using String.valueOf(Object) in toString() implementations can be especially beneficial because we often use the toString() method when debugging and the last thing we need in such cases is another exception encountered while trying to see the current state of our data. Of course, one can also implement toString() methods with one's own checks for null or, even better, one can use something like ToStringBuilder. However, the availability of String.valueOf(Object) is certainly something worth keeping in mind and is something I find myself using fairly often. Many of us have found fewer lines of code to generally be more clear and String.valueOf(Object) can be much more clear than explicitly checking an object for null before invoking its toString() implementation.

Finally, the String class provides many overloaded valueOf methods. In addition to the version that was the focus of this blog post (accepts an Object), the other overloaded versions of valueOf accept primitive data types and arrays of primitive data types.


Conclusion

Regardless of what the future brings in terms of improved null handling in Java, there are many tactics we can take today to reduce the unwanted (sometimes we actually do want them thrown!) occurrences of NullPointerException. One of these is to use String.valueOf(Object) when appropriate.


Additional Resources

No comments: