186

What is the best way to concatenate a list of String objects? I am thinking of doing this way:

List<String> sList = new ArrayList<String>();

// add elements

if (sList != null)
{
    String listString = sList.toString();
    listString = listString.subString(1, listString.length() - 1);
}

I somehow found this to be neater than using the StringBuilder/StringBuffer approach.

Any thoughts/comments?

Jeff Olson
  • 6,323
  • 2
  • 22
  • 26
Jagmal
  • 5,726
  • 9
  • 35
  • 35

19 Answers19

394

Use one of the the StringUtils.join methods in Apache Commons Lang.

import org.apache.commons.lang3.StringUtils;

String result = StringUtils.join(list, ", ");

If you are fortunate enough to be using Java 8, then it's even easier...just use String.join

String result = String.join(", ", list);
Jeff Olson
  • 6,323
  • 2
  • 22
  • 26
  • 1
    Alternatively for Guava users: `Joiner.on(', ').join(Collection)` – froginvasion Jun 13 '16 at 10:40
  • 4
    Yup, every time: Bloch, "Effective Java", Item 47: "Know and use the libraries". The Apache Commons libraries should be the first thing to put in your build file (hopefully Gradle). As Item 47 concludes: "To summarize, don't reinvent the wheel". – mike rodent Nov 20 '16 at 17:44
  • 2
    Using Guava API: `Joiner.on(', ').skipNulls().join(Iterable>)`. To represent null values you can use `Joiner.on(',').useForNull("#").join(Iterable>)` – savanibharat Jan 11 '17 at 00:41
  • @Gojir4 maybe you should check java version >8 before you use similar language, see the duplicate – Petter Friberg Aug 21 '18 at 15:15
  • 2
    As of Java 8 String#join is preferable (no library necessary). – Nils-o-mat May 11 '19 at 15:44
211

Using Java 8+

String str = list.stream().collect(Collectors.joining())

or even

String str = String.join("", list);
Andrei N
  • 4,716
  • 4
  • 29
  • 29
  • `String str = String.join("", list);` probably needs some kind of separator like `,` or `" "` right? – kellyfj Jan 24 '23 at 19:36
46

Your approach is dependent on Java's ArrayList#toString() implementation.

While the implementation is documented in the Java API and very unlikely to change, there's a chance it could. It's far more reliable to implement this yourself (loops, StringBuilders, recursion whatever you like better).

Sure this approach may seem "neater" or more "too sweet" or "money" but it is, in my opinion, a worse approach.

Jack Leow
  • 21,945
  • 4
  • 50
  • 55
  • 8
    I agree that I don't like the OP's way of doing it, but for different reasons. (1) Doing it this way disguises the intent of the code (whereas something like `StringUtils.join` is perfectly readable). (2) It's not flexible, since you might want to change the delimiter. (Just replacing `", "` in the final string isn't a good way, since there could be `", "` embedded in the original strings.) So even though, as you say, `ArrayList.toString` probably won't change, I still wouldn't go this route. – Tim Goodman Jan 18 '13 at 16:25
  • 4
    You can be 99.9% certain that ArrayList.toString won't change unless it becomes an attack vector somehow; backward compatibility will see to that. But I agree that using toString() is still a bad idea. – Barry Kelly Oct 24 '13 at 09:00
45

A variation on codefin's answer

public static String concatStringsWSep(Iterable<String> strings, String separator) {
    StringBuilder sb = new StringBuilder();
    String sep = "";
    for(String s: strings) {
        sb.append(sep).append(s);
        sep = separator;
    }
    return sb.toString();                           
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
29

If you are developing for Android, there is TextUtils.join provided by the SDK.

Chunliang Lyu
  • 1,750
  • 1
  • 20
  • 34
23

This is the most elegant and clean way I've found so far:

list.stream().collect(Collectors.joining(delimiter));
Aleksandar Jovanovic
  • 1,127
  • 1
  • 11
  • 14
  • 3
    IntelliJ gives me a hint that I can improve things by changing this solution to the String.join example from other answers with the following reasoning "Reports stream API call chains which can be simplified. It allows to avoid creating redundant temporary objects when traversing a collection." – chrismacp Apr 09 '19 at 14:01
17

Guava is a pretty neat library from Google:

Joiner joiner = Joiner.on(", ");
joiner.join(sList);
andras
  • 6,339
  • 6
  • 26
  • 22
7

I prefer String.join(list) in Java 8

Manish Mulani
  • 7,125
  • 9
  • 43
  • 45
7

Have you seen this Coding Horror blog entry?

The Sad Tragedy of Micro-Optimization Theater

I am not shure whether or not it is "neater", but from a performance-standpoint it probably won't matter much.

Kevin
  • 30,111
  • 9
  • 76
  • 83
sdfx
  • 21,653
  • 4
  • 26
  • 34
5

ArrayList inherits its toString()-method from AbstractCollection, ie:

public String toString() {
    Iterator<E> i = iterator();
    if (! i.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = i.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! i.hasNext())
            return sb.append(']').toString();
        sb.append(", ");
    }
}

Building the string yourself will be far more efficient.


If you really want to aggregate the strings beforehand in some sort of List, you should provide your own method to efficiently join them, e.g. like this:

static String join(Collection<?> items, String sep) {
    if(items.size() == 0)
        return "";

    String[] strings = new String[items.size()];
    int length = sep.length() * (items.size() - 1);

    int idx = 0;
    for(Object item : items) {
        String str = item.toString();
        strings[idx++] = str;
        length += str.length();
    }

    char[] chars = new char[length];
    int pos = 0;

    for(String str : strings) {
        str.getChars(0, str.length(), chars, pos);
        pos += str.length();

        if(pos < length) {
            sep.getChars(0, sep.length(), chars, pos);
            pos += sep.length();
        }
    }

    return new String(chars);
}
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • 1
    I hope you did benchmarks to confirm that your implementation is better because it not really cleaner than the toString implementation of AbstractCollection. Reader has to really focus on your implementation to understand something while the other is really easy to read. – Matthieu BROUILLARD Apr 02 '09 at 19:05
5

It seems to me that the StringBuilder will be quick and efficient.

The basic form would look something like this:

public static String concatStrings(List<String> strings)
{
    StringBuilder sb = new StringBuilder();
    for(String s: strings)
    {
        sb.append(s);
    }
    return sb.toString();       
}

If that's too simplistic (and it probably is), you can use a similar approach and add a separator like this:

    public static String concatStringsWSep(List<String> strings, String separator)
{
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < strings.size(); i++)
    {
        sb.append(strings.get(i));
        if(i < strings.size() - 1)
            sb.append(separator);
    }
    return sb.toString();               
}

I agree with the others who have responded to this question when they say that you should not rely on the toString() method of Java's ArrayList.

codefin
  • 304
  • 1
  • 5
4

Rather than depending on ArrayList.toString() implementation, you could write a one-liner, if you are using java 8:

String result = sList.stream()
                     .reduce("", String::concat);

If you prefer using StringBuffer instead of String since String::concat has a runtime of O(n^2), you could convert every String to StringBuffer first.

StringBuffer result = sList.stream()
                           .map(StringBuffer::new)
                           .reduce(new StringBuffer(""), StringBuffer::append);
Sreekanth
  • 293
  • 3
  • 6
  • 1
    FYI this implementation is O(n^2) as String::concat copies both strings onto a new buffer: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.concat%28java.lang.String%29 – tep Jan 09 '17 at 04:57
4

I somehow found this to be neater than using the StringBuilder/StringBuffer approach.

I guess it depends on what approach you took.

The AbstractCollection#toString() method simply iterates over all the elements and appends them to a StringBuilder. So your method may be saving a few lines of code but at the cost of extra String manipulation. Whether that tradeoff is a good one is up to you.

Kevin
  • 30,111
  • 9
  • 76
  • 83
  • The extra String manipulation you are talking about is creating subString at the end, right? – Jagmal Feb 07 '09 at 14:58
  • @Jagmal, yes. If this code gets run a lot, then you should profile to see if its a problem for you. Otherwise you should be OK. One thing to keep in mind is that the format of the internal toString() might change in a future JDK version. – Kevin Feb 07 '09 at 15:01
  • @Kevin: Yes, implementation of toString changing in future JDK version sound good enough reason for not doing it, I suppose. :) – Jagmal Feb 07 '09 at 15:03
  • @Jagmal: subString is really hurting you. If your listString takes n bytes, then subString temporary adds another n-4 bytes to the heap. By using StringBuilder you will receive your n-4 from "first try" – LicenseQ Feb 08 '09 at 02:11
  • @LicenseQ: I don't know if this statement was correct at the time you wrote it, but it's not correct for any recent release of Java. The implementation of String.substring is smarter than that: it does not copy the character buffer of the original string, it simply creates a new String object pointing to the same buffer and uses a nonzero "offset" field to indicate that the string doesn't start from the beginning of the buffer. Substring allocates a String object containing three ints (count, hash and offset) and a reference to the pre-existing buffer, nothing more. – Nate C-K Jul 12 '12 at 15:48
3

Next variation on Peter Lawrey's answer without initialization of a new string every loop turn

String concatList(List<String> sList, String separator)
{
    Iterator<String> iter = sList.iterator();
    StringBuilder sb = new StringBuilder();

    while (iter.hasNext())
    {
        sb.append(iter.next()).append( iter.hasNext() ? separator : "");
    }
    return sb.toString();
}
2

In java 8 you can also use a reducer, something like:

public static String join(List<String> strings, String joinStr) {
    return strings.stream().reduce("", (prev, cur) -> prev += (cur + joinStr));
}
2

Assuming it's faster to just move a pointer / set a byte to null (or however Java implements StringBuilder#setLength), rather than check a condition each time through the loop to see when to append the delimiter, you could use this method:

public static String Intersperse (Collection<?> collection, String delimiter)
{
    StringBuilder sb = new StringBuilder ();
    for (Object item : collection)
    {
        if (item == null) continue;
        sb.append (item).append (delimiter);
    }
    sb.setLength (sb.length () - delimiter.length ());
    return sb.toString ();
}
1

if you have json in your dependencies.you can use new JSONArray(list).toString()

programer8
  • 567
  • 1
  • 6
  • 17
1

Depending on the need for performance and amount of elements to be added, this might be an ok solution. If the amount of elements are high, the Arraylists reallocation of memory might be a bit slower than StringBuilder.

georg
  • 2,199
  • 16
  • 11
  • Can you please explain "the Arraylists reallocation of memory might be a bit slower than StringBuilder." – Jagmal Feb 07 '09 at 15:01
  • Arraylist is built on standard arrays and these requires you to set an size of the array(like `int[] a = new int[10];`). This is what the ArrayList is doing for you and as you put in new elements the ArrayList will have to make a larger array and copy all the elements over to the new one. – georg Feb 07 '09 at 15:06
1

Using the Functional Java library, import these:

import static fj.pre.Monoid.stringMonoid;
import static fj.data.List.list;
import fj.data.List;

... then you can do this:

List<String> ss = list("foo", "bar", "baz");
String s = stringMonoid.join(ss, ", ");

Or, the generic way, if you don't have a list of Strings:

public static <A> String showList(List<A> l, Show<A> s) {
  return stringMonoid.join(l.map(s.showS_()), ", ");
}
Apocalisp
  • 34,834
  • 8
  • 106
  • 155