Best way to convert an ArrayList to a string

Viewed 1356516

I have an ArrayList that I want to output completely as a String. Essentially I want to output it in order using the toString of each element separated by tabs. Is there any fast way to do this? You could loop through it (or remove each element) and concatenate it to a String but I think this will be very slow.

27 Answers

Java 8 introduces a String.join(separator, list) method; see Vitalii Federenko's answer.

Before Java 8, using a loop to iterate over the ArrayList was the only option:

DO NOT use this code, continue reading to the bottom of this answer to see why it is not desirable, and which code should be used instead:

ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

String listString = "";

for (String s : list)
{
    listString += s + "\t";
}

System.out.println(listString);

In fact, a string concatenation is going to be just fine, as the javac compiler will optimize the string concatenation as a series of append operations on a StringBuilder anyway. Here's a part of the disassembly of the bytecode from the for loop from the above program:

   61:  new #13; //class java/lang/StringBuilder
   64:  dup
   65:  invokespecial   #14; //Method java/lang/StringBuilder."<init>":()V
   68:  aload_2
   69:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  aload   4
   74:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   77:  ldc #16; //String \t
   79:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   82:  invokevirtual   #17; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

As can be seen, the compiler optimizes that loop by using a StringBuilder, so performance shouldn't be a big concern.

(OK, on second glance, the StringBuilder is being instantiated on each iteration of the loop, so it may not be the most efficient bytecode. Instantiating and using an explicit StringBuilder would probably yield better performance.)

In fact, I think that having any sort of output (be it to disk or to the screen) will be at least an order of a magnitude slower than having to worry about the performance of string concatenations.

Edit: As pointed out in the comments, the above compiler optimization is indeed creating a new instance of StringBuilder on each iteration. (Which I have noted previously.)

The most optimized technique to use will be the response by Paul Tomblin, as it only instantiates a single StringBuilder object outside of the for loop.

Rewriting to the above code to:

ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

StringBuilder sb = new StringBuilder();
for (String s : list)
{
    sb.append(s);
    sb.append("\t");
}

System.out.println(sb.toString());

Will only instantiate the StringBuilder once outside of the loop, and only make the two calls to the append method inside the loop, as evidenced in this bytecode (which shows the instantiation of StringBuilder and the loop):

   // Instantiation of the StringBuilder outside loop:
   33:  new #8; //class java/lang/StringBuilder
   36:  dup
   37:  invokespecial   #9; //Method java/lang/StringBuilder."<init>":()V
   40:  astore_2

   // [snip a few lines for initializing the loop]
   // Loading the StringBuilder inside the loop, then append:
   66:  aload_2
   67:  aload   4
   69:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  pop
   73:  aload_2
   74:  ldc #15; //String \t
   76:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   79:  pop

So, indeed the hand optimization should be better performing, as the inside of the for loop is shorter and there is no need to instantiate a StringBuilder on each iteration.

Download the Apache Commons Lang and use the method

 StringUtils.join(list)

 StringUtils.join(list, ", ") // 2nd param is the separator.

You can implement it by yourself, of course, but their code is fully tested and is probably the best possible implementation.

I am a big fan of the Apache Commons library and I also think it's a great addition to the Java Standard Library.

Loop through it and call toString. There isn't a magic way, and if there were, what do you think it would be doing under the covers other than looping through it? About the only micro-optimization would be to use StringBuilder instead of String, and even that isn't a huge win - concatenating strings turns into StringBuilder under the covers, but at least if you write it that way you can see what's going on.

StringBuilder out = new StringBuilder();
for (Object o : list)
{
  out.append(o.toString());
  out.append("\t");
}
return out.toString();

Most Java projects often have apache-commons lang available. StringUtils.join() methods is very nice and has several flavors to meet almost every need.

public static java.lang.String join(java.util.Collection collection,
                                    char separator)


public static String join(Iterator iterator, String separator) {
    // handle null, zero and one elements before building a buffer 
    Object first = iterator.next();
    if (!iterator.hasNext()) {
        return ObjectUtils.toString(first);
    }
    // two or more elements 
    StringBuffer buf = 
        new StringBuffer(256); // Java default is 16, probably too small 
    if (first != null) {
        buf.append(first);
    }
    while (iterator.hasNext()) {
        if (separator != null) {
            buf.append(separator);
        }
        Object obj = iterator.next();
        if (obj != null) {
            buf.append(obj);
        }
    }
    return buf.toString();
}

Parameters:

collection - the Collection of values to join together, may be null

separator - the separator character to use

Returns: the joined String, null if null iterator input

Since: 2.3

An elegant way to deal with trailing separation characters is to use Class Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator("\t");
for (String each: list) buf.append(sep).append(each);
String s = buf.toString();

The toString method of Class Separator returns the separater, except for the first call. Thus we print the list without trailing (or in this case) leading separators.

It's an O(n) algorithm either way (unless you did some multi-threaded solution where you broke the list into multiple sublists, but I don't think that is what you are asking for).

Just use a StringBuilder as below:

StringBuilder sb = new StringBuilder();

for (Object obj : list) {
  sb.append(obj.toString());
  sb.append("\t");
}

String finalString = sb.toString();

The StringBuilder will be a lot faster than string concatenation because you won't be re-instantiating a String object on each concatenation.

In case you happen to be on Android and you are not using Jack yet (e.g. because it's still lacking support for Instant Run), and if you want more control over formatting of the resulting string (e.g. you would like to use the newline character as the divider of elements), and happen to use/want to use the StreamSupport library (for using streams on Java 7 or earlier versions of the compiler), you could use something like this (I put this method in my ListUtils class):

public static <T> String asString(List<T> list) {
    return StreamSupport.stream(list)
            .map(Object::toString)
            .collect(Collectors.joining("\n"));
}

And of course, make sure to implement toString() on your list objects' class.

If you don't want the last \t after the last element, you have to use the index to check, but remember that this only "works" (i.e. is O(n)) when lists implements the RandomAccess.

List<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

StringBuilder sb = new StringBuilder(list.size() * apprAvg); // every apprAvg > 1 is better than none
for (int i = 0; i < list.size(); i++) {
    sb.append(list.get(i));
    if (i < list.size() - 1) {
        sb.append("\t");
    }
}
System.out.println(sb.toString());
Related