Array puzzler

6

I learned some interesting things about the Arrays class in JDK 1.5 today from the presentation
The Continuing Adventures of Java™ Puzzlers: Tiger Traps by Josh Bloch and Neal Gafter. Specifically puzzler #7 “Fib O’Nacci”. I’ll cut to the chase and just show the heart of it – consider this example in Java 5:

    System.out.println(Arrays.asList(new int[] { 1, 2, 3 }));

You might expect that this prints [1, 2, 3] because in JDKs prior to Java 1.5, it did. But in Java 5, you’ll instead get something like [[I@10b62c9]. The reason is that in Java 1.5, Arrays.asList() was changed to take a varargs. That’s really cool because you can then create lists like this (taking advantage of autoboxing as well):

    List list = Arrays.asList(1, 2, 3, 4, 5);

But it really sucks in this case because varags only works with arrays of Object refs. Since in the puzzler we’re passing an array of primitives, the entire array is treated as the first item in the varargs array and this is effectively equivalent to Arrays.asList(new Object[] { new int[] { 1, 2, 3} }), which explains why you see an array containing the typical gross array toString in the result.

So, this was a bit of a goof. I listened to a podcast version of this same presentation from java.no and Josh said this was his fault, but I guess it was not discovered until too late. He also mentioned that there are a host of new methods on the Arrays class, which I was not previously aware of. Specifically, there are toString, equals, hashCode, deepToString, deepEquals, and deepHashCode methods, all of which are overloaded to handle both primitive and Object arrays.

So, if you are using Arrays.asList() specifically to get a printable version of the array (a common usage), you can instead use the toString() method, which will work for any array type in JDK 1.5:

    System.out.println(Arrays.toString(new int[] { 1, 2, 3 }));

This will, as expected, print [1, 2, 3]. The deep versions can be used to print, compare, and create hashcodes for arrays of arrays (matrices), etc. So, I’m sure I’ve got code somewhere that’s doing this gross work for arrays which can now be simplified.

On a tangent, why doesn’t the array toString actually print something useful in the first place so that we could just say:

    System.out.println(new int[] { 1, 2, 3});

and not have to involve the Arrays class? Same with equals and hashCode. Is there some reason arrays shouldn’t work more like collections? That’s always seemed goofy to me.

Comments

6 Responses to “Array puzzler”
  1. Guillaume Poirier says:

    Actually, before JDK 1.5, the example you gave would not even have compiled, int[] is not a valid argument for Object[]. To my knowledge, there’s no case where the varargs break backward compatibility, the only thing that happens is sometimes you get warning at compile time for example if you pass null as a parameter for varargs. But the behavior will be the same as before 1.5.

  2. Alex says:

    Doh! Absolutely right. Thank you Guillaume.

  3. You already mentioned Arrays.deepEquals in your post. Actually, I wished an Objects utility class would be introduced, which provides null-safe equals and deepEquals methods to compare objects (and maybe other null-safe ones). Arrays.deepEquals already does so, only one has to wrap objects in single-object arrays (which is a bit goofy).

  4. Simon says:

    What I wish the Object class had is a method
    boolean is(Object obj)
    which wraps ( myObj == obj ) so have object-orientated method when know myObj is non-null.

    For Stefan:
    (1) a null-safe equals() method which depends upon (2) areIdentical() which hides == so that I don’t use single = by mistake.

    I have these in an Object utility class I call ObjectUtil.

    /**
    * Tests any two Objects ( including the case of two nulls ) for equality.
    *
    * @param o1 the first Object to be tested
    * @param o2 the second Object to be tested
    *
    * @return true if the two objects are equal, false otherwise
    */
    public final static boolean areEqual( final Object o1, final Object o2 ){
    return areIdentical( o1, o2 ) || ( notNull( o1 ) && o1.equals( o2 ) );
    }

    /**
    * Tests any two Objects for identity.
    *
    * @param o1 the first Object to be tested
    * @param o2 the second Object to be tested
    *
    * @return true if the two objects are identical, false otherwise
    */
    public final static boolean areIdentical( final Object o1, final Object o2 ){
    return ( o1 == o2 );
    }

  5. Simon says:

    Previous post code example referenced a method
    notNull( o1 ) which was omitted (apologies to Stefan).
    Here are notNull() and isNull() upon which it depends.

    /**
    * Tests whether aObject is null.
    *
    * @param aObject the Object to be tested for nullity
    *
    * @return true if aObject is null, false otherwise
    */
    public final static boolean isNull( final Object aObject ){
    return areIdentical( aObject, null );
    }

    /**
    * Tests whether aObject is non-null.
    *
    * @param aObject the Object to be tested for non-nullity
    *
    * @return false if aObject is null, true otherwise
    */
    public final static boolean notNull( final Object aObject ){
    return not( isNull( aObject ) );
    }

  6. Simon: Unfortunately, Object.equals does not cover arrays (which only tests for identity). That’s the main reason for me using Arrays.deepEquals which cares about the various primitive arrays, too.

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!