Tuesday, 18 September 2012

Puzzle 12: ABC


This puzzle asks the musical question, What does this program print?
public class Abc {

    public static void main(String[] args) {

        String letters = "ABC";

        char[] numbers = { '1', '2', '3' };

        System.out.println(letters + " easy as " + numbers);

    }

}


Solution 12: ABC

One would hope that the program prints ABC easy as 123. Unfortunately, it does not. If you ran it, you found that it prints something like ABC easy as [C@16f0472. Why is the output so ugly?
Although char is an integral type, many libraries treat it specially, because char values usually represent characters rather than integers. For example, passing a char value to println prints a Unicode character rather than its numerical code. Character arrays get similar special treatment: The char[] overloading of println prints all of the characters contained in the array, and the char[] overloadings of String.valueOf and StringBuffer.append behave analogously.
The string concatenation operator, however, is not defined in terms of these methods. It is defined to perform string conversion on both of its operands and then to concatenate the resulting strings. String conversion for object references, which include arrays, is defined as follows [JLS 15.18.1.1]:
If the reference is null, it is converted to the string "null". Otherwise, the conversion is performed as if by an invocation of the toString method of the referenced object with no arguments; but if the result of invoking the toString method is null, then the string "null" is used instead.
So what is the behavior of invoking toString on a non-null char array? Arrays inherit the toString method from Object [JLS 10.7], whose specification says, "Returns a string consisting of the name of the class of which the object is an instance, the at-sign character '@', and the unsigned hexadecimal representation of the hash code of the object" [Java-API]. The specification for Class.getName says that the result of invoking this method on the class object for char[] is the string "[C". Putting it all together gives the ugly string printed by our program.
There are two ways to fix it. You can explicitly convert the array to a string before invoking string concatenation:
System.out.println(letters + " easy as " +

                   String.valueOf(numbers));


Alternatively, you can break the System.out.println invocation in two to make use of the char[] overloading of println:
System.out.print(letters + " easy as ");

System.out.println(numbers);


Note that these fixes work only if you invoke the correct overloading of the valueOf or println method. In other words, they depend critically on the compile-time type of the array reference. The following program illustrates this dependency. It looks as though it incorporates the second fix described, but it produces the same ugly output as the original program because it invokes the Object overloading of println instead of the char[] overloading:
// Broken - invokes the wrong overloading of println!

class Abc {

    public static void main(String[] args) {

        String letters = "ABC";

        Object numbers = new char[] { '1', '2', '3' };

        System.out.print(letters + " easy as ");

        System.out.println(numbers); // Invokes println(Object)

    }

}


To summarize, char arrays are not strings. To convert a char array to a string, invoke String.valueOf(char[]). Some library methods do provide stringlike support for char arrays, typically having one overloading for Object and another for char[]; only the latter has the desired behavior.
The lesson for language designers is that the char[] type should probably have overridden toString to return the characters contained in the array. More generally, the array types should probably have overridden toString to return a string representation of the contents of the array.

No comments:

Post a Comment

Your comments are welcome!