Home > Programming > Be Careful When Converting Java Arrays to Lists

Be Careful When Converting Java Arrays to Lists

May 1, 2010

Unfortunately not everything that should be trivial actually is. One example is converting Java arrays to lists. Of course, there is Arrays.toList, but using this method carelessly will almost certainly lead to nasty surprises. To see what I mean consider the following program and try to predict its output:

package com.wordpress.mlangc.arrays;

import java.util.Arrays;

public class ArraysToList
{
    public static void main(final String[] args)
    {
        System.out.println(
                Arrays.asList(new String[] { "a", "b" }));
        
        System.out.println(
                Arrays.asList(new Integer[] { 1, 2 }));
        
        System.out.println(
                Arrays.asList(new int[] { 1, 2 }));
        
        System.out.println(
                Arrays.asList(new String[] { "a", "b" }, "c"));
    }
}

As the Javadocs for Arrays.asList are quite vague, I can’t blame you for having some difficulties coming to a conclusion, so here is the answer step by step:

  • Line 9 prints “[a, b]” to our shiny little console which is pretty much what one would expect from a sane API, so we are happy.
  • The same is true for line 12 which results in “[1, 2]”.
  • Line 15 however is different, not only because 15 is not 12, but also because an int is not an Integer, and therefore prints “[[I@39172e08]” to our console, that is not shiny anymore. Instead of a list containing two Integer objects, we got a list containing the array as its sole element.
  • After what we have seen above, it should not take you by surprise that line 18 results in another mess that looks like “[[Ljava.lang.String;@20cf2c80, c]”.

So, what happened? The first two print statements worked as expected, because the Java Language Specification states that calling a method with signature foo(T… t) like foo(new T[] { bar, baz }) is semantically equivalent to foo(bar, baz). In Arrays.asList T is a type parameter, so it has to be an Object, and this is not true for int, but for int[]. Thats why the statement in line 16 is equivalent to

Arrays.asList(new Object[] { new int[] { 1, 2 } })

Last but not least, the statement in line 19 called for trouble from the very beginning. We told the compiler that we want a list containing an array of strings and a string, which is exactly what we got.

So far for the explanation, but there is something else we can learn from that: The real source of confusion is not that varargs feature is badly designed; I would rather say that the opposite is true. The problem is that Arrays.asList violates EffJava2 Item 42, which explains clearly, in fact giving Arrays.asList as a bad example, why you should be quite careful when designing APIs that use Java varargs. I won’t repeat the reasoning of the book here, as you really should read it yourself, but for the sake of completeness I have to point out that the problematic statements from above would have been rejected by the compiler back in the old days of Java 1.4, which was a good thing. We can still use Arrays.asList today, but doing so safely requires us to be aware of the subtillities we are facing. Here are a few rules for converting arrays to lists, that guarantee nothing unexpected happens:

  • If you convert to a list just to convert to a string, use Arrays.toString instead. It does what you want all the time and also works for arrays of primitives.
  • If you want to convert an array of primitives to a list of boxed primitives, take advantage of Apache Commons Lang, which most likely is a dependency of your project already anyway, and use ArrayUtils.toObject like this:

    List<Integer> list = Arrays.asList(ArrayUtils.toObject(new int[] { 1, 2 }));
    

    Note however that lists of boxed primitives should not generally be preferred over arrays containing primitives.

  • If you want to convert an array of object references, use Arrays.asList directly:

     List<String> list = 
               Arrays.asList(new String[] { "a", "b" });
    

    Don’t forget to make sure that the people you work with won’t emulate you carelessly.

Of course, you can also choose to just remember that Arrays.asList might behave unexpectedly and use plain for loops instead, but that clutters your code and comes with a performance penalty.

Advertisements
Categories: Programming Tags: , ,
  1. May 3, 2010 at 05:36

    Very nice article. Keep posting tips like this.

  2. May 3, 2010 at 09:11

    Your examples are a bit misleading especially to people who don’t know the signature of Arrays.asList like myself. No sane person would write
    Arrays.asList(new String[] { “a”, “b” }));
    let alone
    Arrays.asList(new String[] { “a”, “b” }, “c”));
    Obviously the API is designed to be used as
    Arrays.asList(“a”, “b”);
    and
    Arrays.asList(“a”, “b”, “c”);
    I am not a Java developer and I have not tested this but I believe it would produce the expected result and it is simpler and more readable. That being said you would normally use this methods with array variables like this
    Arrays.asList(someArray);
    or
    Arrays.asList(someArray, “s”);
    While the second example is still confusing and probably is bad API design I would argue that it is much less confusing than what your original example suggest (i.e. the API is not THAT bad).

    The example with the primite types is something that one should really be worried about. It is not manifestation of bad API design but instead is yet another example of the bad language design of Java’s primite types.

    • May 3, 2010 at 18:50

      It is perfectly clear that nobody would write “Arrays.asList(new String[] { “a”, “b” }))” in a real program. I did so for demonstration purposes only. Having said that, the API *is* designed to be used with a single array argument, as long as it’s an array of reference types. Last but not least: I’m perfectly aware that Java has some issues from a language design point of view, but the problems with Arrays.asList could have been easily avoided by leaving the method signature as it was in Java 1.4.

      • May 3, 2010 at 19:20

        The bad API design may be a fact but it is not evident from your example. If a developer writes the code in my example I would blame the developer for not being able to read the method signature. Probably you should try better explaination or better example of why the API is bad.

      • May 3, 2010 at 20:17

        Stilgar :

        Probably you should try better explaination or better example of why the API is bad.

        The API is bad because it can be easily used incorrectly. Java 1.4 would have flagged the problematic statements as compiler errors, but when varargs were added to Java 1.5, the API designers decided to “improve” Arrays.asList to allow things like Arrays.asList(“foo”, “bar”, “baz”), without noticing that by doing so, they seriously compromised type safety. If you are really interested, grab yourself a copy of EffJava2 and read Item 42. It contains a far more elaborate and detailed discussion about these issues than I can give you here.

  3. May 3, 2010 at 10:06

    Hi. I agree with you. I had problems with this in past.
    Thank you for this summary.

  4. May 3, 2010 at 14:57

    Just a brief remarking concerning adding dependencies to, e.g., Apache Commons Lang:

    It cannot safely be assumed to already be present, and adding a dependency for a single use or for a not-to-complicated function is usually a bad idea. In this case, an alternative would be to write an own function that abstracts the conversion, which may or may not use the ACL at any given time. Even when ACL already is present, such an abstraction will often (by no means always) pay off from a design POV.

    • May 3, 2010 at 19:02

      Well, Commons Lang provides quite a few other useful things too, so I really wouldn’t mind having it as a dependency.

      • May 5, 2010 at 12:20

        Which is perfectly alright. However, bear in mind that there may be situations where there are external restrictions on what type of libraries may be used, where the (other) corresponding functionality is already covered by other libraries, where there is need to minimize the run-time memory footprint, or other reasons not to include it.

  5. dotnetnewbie
    May 3, 2010 at 21:06

    Hi,

    Sorry if this is offtopic but I really like the code snippet your presented in your blog. what software do you use to display the code snippet?

    • May 3, 2010 at 22:25

      Nothing special; it’s just the code formatter that comes with WordPress.

  6. Tamil
    May 4, 2010 at 16:28

    I don’t think the API is the one to be blamed. In actual coding scenario, ifyou cast the list in a variable and expect output of List or List for your line 18, 15 examples, you will have a compile error becoz the API return type is not what you expect. The API is just designed to give you what you pass in. If you pass, array and string, it will give you array and string.

  1. May 4, 2010 at 05:30
  2. May 4, 2010 at 06:55
  3. May 23, 2012 at 12:47
Comments are closed.
%d bloggers like this: