22

Given a Collection of Strings, how would you join them in plain Java, without using an external Library?

Given these variables:

Collection<String> data = Arrays.asList("Snap", "Crackle", "Pop");
String separator = ", ";
String joined; // let's create this, shall we?

This is how I'd do it in Guava:

joined = Joiner.on(separator).join(data);

And in Apache Commons / Lang:

joined = StringUtils.join(data, separator);

But in plain Java, is there really no better way than this?

StringBuilder sb = new StringBuilder();
for(String item : data){
    if(sb.length()>0)sb.append(separator);
    sb.append(item);
}
joined = sb.toString();
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588

7 Answers7

15

I'd say the best way of doing this (if by best you don't mean "most concise") without using Guava is using the technique Guava uses internally, which for your example would look something like this:

Iterator<String> iter = data.iterator();
StringBuilder sb = new StringBuilder();
if (iter.hasNext()) {
  sb.append(iter.next());
  while (iter.hasNext()) {
    sb.append(separator).append(iter.next());
  }
}
String joined = sb.toString();

This doesn't have to do a boolean check while iterating and doesn't have to do any postprocessing of the string.

ColinD
  • 108,630
  • 30
  • 201
  • 202
10

May be instead of calling sb.length() again and again in a loop, i have slightly modified.

StringBuilder sb = new StringBuilder();
String separator = "";
for(String item : data){
    sb.append(separator);
    separator=",";
    sb.append(item);
}
joined = sb.toString();
Dead Programmer
  • 12,427
  • 23
  • 80
  • 112
  • 1
    Why the edit? The old String based version was more flexible and the new version adds an unnecessary space to the beginning. – Sean Patrick Floyd Feb 08 '11 at 12:25
  • 1
    sorry too much premature optimization :) – Dead Programmer Feb 08 '11 at 12:41
  • This is how I do it, too. It avoids an extra `if` in favour of an extra non-conditional statement, which makes it preferable in the framework of Bob Martin's "Transformation Priority Premise" (http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html). It is also probably faster to execute (branches have a performance penalty in the processor whereas repeated assignment is likely to have very little cost; it is also probably easier for a loop unroller to optimize away the extra statements in this form). – Jules Nov 14 '13 at 06:50
8

Java 8+

joined =String.join(separator, data);

Java 7 and below

StringBuilder sb = new StringBuilder();
boolean first = true;
for(String item : data){
    if(!first || (first = false)) sb.append(separator);
    sb.append(item);
}
joined = sb.toString();
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 2
    I don't like using `if(first)` because it's unnecessarily verbose, but I have to agree that `if(!first || (first = false))` is nifty (+1) – Sean Patrick Floyd Feb 08 '11 at 12:22
  • I was going to give the Checkmark to @Suresh S, but I don't like the current edit, so here goes – Sean Patrick Floyd Feb 08 '11 at 12:26
  • Instead of your first example, you could just do: `joined = data.toString().substring(1, joined.length() - 2);` – Dan King Mar 14 '14 at 16:33
  • @DanKing, you can't use `joined.length()` before you've assigned meaningful data to `joined`. – aioobe Apr 20 '15 at 09:31
  • Quite right @aioobe - I'll get my coat! ;-) Although I notice you've completely changed your answer since I made that comment, so it no longer makes sense anyway - if it were right in the first place, which it wasn't... – Dan King Apr 20 '15 at 19:06
5

In Java 8:

String.join (separator, data)
Jules
  • 14,841
  • 9
  • 83
  • 130
1

Collectors can join Streams which is very useful if you want to filter or map your data.

String joined = Stream.of("Snap", "Crackle", "Pop")
        .map(String::toUpperCase)
        .collect(Collectors.joining(", "));

To get a Stream from a Collection, call stream() on it:

Arrays.asList("Snap", "Crackle", "Pop").stream()
Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
1

Different intentions:

For third-part tools, such as Guava's Joiner, which is not fast, but very flexible. There are many option methods to customize join behavior, such as skipNulls().

Plain Java is fast, but it needs you write some lines of codes by yourself.

卢声远 Shengyuan Lu
  • 31,208
  • 22
  • 85
  • 130
  • 4
    I'd say `Joiner` is plenty fast, especially if you reuse a pre-built `Joiner` for a specific separator. It uses a `StringBuilder` for the building and it has a really interesting way of ensuring that it only adds a separator at the right places that doesn't require any boolean check each iteration. – ColinD Feb 11 '11 at 21:14
0

Your proposition is a possible solution, another common one is:

for (String item: data) {
    sb.append(item)
    sb.append(separator);
}
String joined = sb.substring(0, sb.length() - separator.length())
Nicolas
  • 24,509
  • 5
  • 60
  • 66
  • More like: `sb.substring(0, sb.length() - separator.length())`. But I'd call this an awful hack – Sean Patrick Floyd Feb 08 '11 at 08:38
  • 2
    It is. In fact you have to choose between an hack, a often useless test and the use of an iterator to extract the first member of the list. No perfect solution. – Nicolas Feb 08 '11 at 08:43