11

I have never found a neat(er) way of doing the following.

Say I have a list/array of strings.

abc
def
ghi
jkl

And I want to concatenate them into a single string delimited by a comma as follows:

abc,def,ghi,jkl

In Java, if I write something like this (pardon the syntax),

String[] list = new String[] {"abc","def","ghi","jkl"};
String str = null;
for (String s : list)
{
   str = str + s + "," ;
}

System.out.println(str);

I'll get

abc,def,ghi,jkl,  //Notice the comma in the end

So I have to rewrite the above for loop as follows

...
for (int i = 0; i < list.length; i++)
{
   str = str + list[i];
   if (i != list.length - 1)
   {
     str = str + ",";
   }
}
...

Can this be done in a more elegant way in Java?

I would certainly use a StringBuilder/Buffer for efficiency, but I wanted to illustrate the case in point without being too verbose. By elegant, I mean a solution that avoids the ugly(?) if check inside the loop.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rahul
  • 12,886
  • 13
  • 57
  • 62
  • 1
    See http://stackoverflow.com/questions/205555/the-most-sophisticated-way-for-creating-comma-separated-strings-from-a-collection/205712#205712 – gimel Oct 29 '09 at 08:08
  • http://stackoverflow.com/questions/285523 – toolkit Oct 29 '09 at 09:47

8 Answers8

11

Using Guava's (formerly google-collections) joiner class:

Joiner.on(",").join(list)

Done.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andreas Petersson
  • 16,248
  • 11
  • 59
  • 91
  • 2
    +1 for google-collections. Yes, yes, I know -- 'an external dependency just for string joining?!?!' -- but it can make your code so much shorter + more expressive that, if you learn the API, it will pay for itself in an hour. :) – Cowan Oct 29 '09 at 08:29
  • 2
    once you take a look at the api, you will use ist for much more than just string joining. – Andreas Petersson Oct 29 '09 at 08:35
  • You right - if external api can be used more that one time - it is best solution. – St.Shadow Oct 29 '09 at 09:08
  • @Thorbjorn: see http://www.youtube.com/watch?v=ZeO_J2OcHYM and http://www.youtube.com/watch?v=9ni_KEkHfto for an in-depth explanation why you should use G-C. it simplifies a lot of the boilerplate when using almost any complex data structure in java. – Andreas Petersson Oct 29 '09 at 11:48
  • 4
    why this is cool: now you want to skip over nulls? Just insert '.skipNulls()' between the 'on' and 'join' calls. Or treat nulls like empty strings? Easy, that's '.useForNull("")'. Etc. – Kevin Bourrillion Nov 04 '09 at 17:38
  • Cool - didn't know of this before. Time to throw away our own `StringUtils.join()` utility... :) – Jonik Nov 04 '09 at 22:15
10

Here is my version: Java Tricks: Fastest Way to Collecting Objects in a String

StringBuilder buffer = new StringBuilder ();
String delim = "";
for (Object o: list)
{
    buffer.append (delim);
    delim = ", "; // Avoid if(); assignment is very fast!
    buffer.append (o);
}
buffer.toString ();

As an additional bonus: If your code in the loop is more complex, then this approach will produce a correct results without complex if()s.

Also note that with modern CPUs, the assignment will happen only in the cache (or probably only in a register).

Conclusion: While this code looks odd at first glance, it has many advantages.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 1
    It is not very pretty - make an initialization of delim every iteration. – St.Shadow Oct 29 '09 at 07:57
  • 1
    Please provide your solution in case of deletion of your blog post. – guerda Oct 29 '09 at 07:57
  • +1 This is what I do also. Do you have performance figures to prove we are right? – KLE Oct 29 '09 at 08:05
  • 1
    @Gonzo: if() is slower than an assign. The assignment is just a pointer operation and it always happens with the same address, so the CPU will probably use registers and keep everything in the cache. Also, the code is compact, the setup is constant (no need to do part of the work outside of the loop) and the code in the loop is always the same. – Aaron Digulla Oct 29 '09 at 08:14
  • Talking about this degree of performance modifications is ridiculous anyway. Isn't it? Well, then show me a usecase where you need to squeeze out this extra nanosecond ... – sfussenegger Oct 29 '09 at 08:15
  • I just did a quick performance test. Statistically speaking, there is no difference whatsoever. Really, an if() is slow? – Reverend Gonzo Oct 29 '09 at 08:15
  • 1
    @Aaron: I'll agree it's more compact. Faster, no. Even if you were writing in direct assembly, that's not going to make a difference. Furthermore, smart compilers will unroll and optimize it anyways. – Reverend Gonzo Oct 29 '09 at 08:18
  • 1
    ... but to speak of clean code: using `builder.setLength(builder.length() - 1);` after the loop is probably the cleanest (and doesn't require a `System.arraycopy(..)` like `deleteCharAt(int)` does) – sfussenegger Oct 29 '09 at 08:18
  • @KLE: No but since my loop doesn't contain conditional code, I'm pretty sure. – Aaron Digulla Oct 29 '09 at 08:33
  • @sfussenegger: It might cause the buffer to allocate more memory because that single character doesn't fit which means you have an `arraycopy`. – Aaron Digulla Oct 29 '09 at 08:35
  • @Gonzo: if() means a branch and that always costs more, even if you don't notice a difference in runtime. At the very best, the CPU branch prediction hardware must run which at least costs power, leading to heat, leading to premature aging of your CPU. ;) Run this code with Java 1.1 and on a Pentium CPU and it still performs. Run it on a Netbook. Also, the code is very clear, readable and obvious and it's very similar in any language. – Aaron Digulla Oct 29 '09 at 09:06
  • 1
    @Aaron Digulla: I was comparing setLength(builder.length() - 1) to deleteCharAt(builder.length()) which are doing the same but the former with far less overhead. You're right that an extra character might cause an extra arraycopy - but chances are minimal, especially for Strings of a size where speed really matters (size of the underlying buffer always doubles). But still it's ridiculous to talk about performance at this level anyway. I really don't care if my CPU dies 5 minutes earlier as it wasn't able to predict branches right. Damn, if it wasn't able to do so it deserved it anyway! ;) – sfussenegger Oct 29 '09 at 10:35
  • @St.Shadow: No, it isn't a performance bug. Because the string litteral in the string pool. It isn't allocation new heap memory. The only operation that happens is simply assigning the variable to a pointer (which points to the string pool). – Martijn Courteaux Sep 22 '11 at 15:05
  • If is quite slow, it causes a brance prediction where it has to guess which way the code is going to branch and fill the cache based on that guess, if it guesses wrong it has to go out and refetch the cach, but even this is silly to worry about since things like this change with CPU & java version. Becoming an expert at optimization means your expertiese will be outdated and probably wrong in two years. – Bill K Jul 30 '13 at 23:00
3
StringBuilder builder = new StringBuilder();
for (String st: list) {
    builder.append(st).append(',');
}
builder.deleteCharAt(builder.length());
String result = builder.toString();

Do not use '+' for string contacenations. It`s slow.

St.Shadow
  • 1,840
  • 1
  • 12
  • 16
2

Look here:

http://snippets.dzone.com/posts/show/91

for a full discussion of this topic.

Erich Kitzmueller
  • 36,381
  • 5
  • 80
  • 102
  • Thanks for the link. I am looking for a more elegant approach (perhaps one that involves fewer lines of code). Performance, in my case, is not such a major concern. – Rahul Oct 29 '09 at 07:54
  • I don not like checks in the look when you can do it outside – St.Shadow Oct 29 '09 at 07:56
2

I'd bet that there are several classes named "StringUtil", "StringsUtil", "Strings" or anything along those lines on the classpath of any medium sized Java project. Most likely, any of them will provide a join function. Here are some examples I've found in my project:

org.apache.commons.lang.StringUtils.join(...)
org.apache.wicket.util.string.Wicket.join(...)
org.compass.core.util.StringUtils.arrayToDelimitedString(...)

As you might want to get rid of some external dependencies in the future, you may want to to something like this:

public static final MyStringUtils {
    private MyStringUtils() {}

    public static String join(Object[] list, String delim) {
        return org.apache.commons.lang.StringUtils.join(list, delim);
    }
}

Now that's what I call "elegant" ;)

sfussenegger
  • 35,575
  • 15
  • 95
  • 119
  • Why would you create a new class just to wrap a call to org.apache.commons.lang.StringUtils.join? How does that remove the external dependencies? – Derek Springer Sep 22 '11 at 18:00
  • 1
    @DerekSpringer it doesn't remove the external dependency. Hiding the dependency in something like MyStringUtils only makes it easier to change the implementation. As a consequence, one could remove the external dependency by changing MyStringUtils class only. – sfussenegger Sep 23 '11 at 07:42
0

check if this is useful:

List<Integer> numbers=new ArrayList<Integer>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    numbers.add(4);
    numbers.add(5);
    numbers.add(6);
    String str = " ";
    for(Integer s:numbers){
    str=str+s+" , ";
    }
    System.out.println(str.substring(0,str.length()-2));
-1

I would use a StringBuffer to implement this feature. String is immutable so everytime you concat two Strings, a new object is created.

More efficient is the use of StringBuffer:

String[] list = new String[] {"abc","def","ghi","jkl"};
StringBuffer str = new StringBuffer();
for (String s : list) {
   str.append(s);
   str.append(",");
}
str.deleteCharAt(str.length());
System.out.println(str); //automatically invokes StringBuffer.toString();
guerda
  • 23,388
  • 27
  • 97
  • 146
  • 1
    You shouldn't use StringBuffer where it's not necessary, it's thread safe and slow, i would use StringBuilder instead. You also have to delete the last comma – Christopher Klewes Oct 29 '09 at 07:58
-1
for (int i = 0; i < list.length-1; i++) {
    str = str + list[i];
    str = str + ",";    
}
str = str + list[list.length-1] 
Roddy of the Frozen Peas
  • 14,380
  • 9
  • 49
  • 99
Peter
  • 5,728
  • 20
  • 23