93

I have a few Set<String>s and want to transform each of these into a single String where each element of the original Set is separated by a whitespace " ". A naive first approach is doing it like this

Set<String> set_1;
Set<String> set_2;

StringBuilder builder = new StringBuilder();
for (String str : set_1) {
  builder.append(str).append(" ");
}

this.string_1 = builder.toString();

builder = new StringBuilder();
for (String str : set_2) {
  builder.append(str).append(" ");
}

this.string_2 = builder.toString();

Can anyone think of a faster, prettier or more efficient way to do this?

Lars Andren
  • 8,601
  • 7
  • 41
  • 56

8 Answers8

182

With commons/lang you can do this using StringUtils.join:

String str_1 = StringUtils.join(set_1, " ");

You can't really beat that for brevity.

Update:

Re-reading this answer, I would prefer the other answer regarding Guava's Joiner now. In fact, these days I don't go near apache commons.

Another Update:

Java 8 introduced the method String.join()

String joined = String.join(",", set);

While this isn't as flexible as the Guava version, it's handy when you don't have the Guava library on your classpath.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 2
    brief, but inflexible. I get "" substituted for null whether I want it or not, and don't have a skip nulls option... this is why we made Joiner for Guava (see other answer). – Kevin Bourrillion Jun 15 '10 at 21:42
  • I usually don't have nulls in my collections, so the brief approach is fine for me, but Guava rocks! Thanks for making that happen... – Sean Patrick Floyd Jun 16 '10 at 04:37
  • Since you don't expect nulls, you'd like your joiner to blow up if you *did* have a null -- which is what Guava's Joiner does by default. :-) – Kevin Bourrillion Jun 17 '10 at 21:53
  • This one is null safe (when set is null) whereas the Guava approach is not – smp7d Jun 07 '17 at 22:18
  • 1
    @smp7d nor should it be. Null is not valid input. Assuming it is would be dangerous. Rejecti null values with precondition checks and you'll never have to worry about null safety again – Sean Patrick Floyd Jun 07 '17 at 22:27
  • @java-addict301 Lots of little things, but the worst is the supposed null-safety everywhere. Read the [Guava section explaining why that is a bad idea](https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained) – Sean Patrick Floyd Oct 11 '17 at 18:58
  • 1
    @SeanPatrickFloyd Hmm, interesting looking read, thanks for the reference. I am very careful, intentional, and minimal with my use of null in general so perhaps I haven't noticed as much. Most importantly however, I always Always ALWAYS check values/parameters for null before use and throw an exception if they are, in almost all cases.. – java-addict301 Oct 11 '17 at 21:18
44

If you are using Java 8, you can use the native

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

method:

Returns a new String composed of copies of the CharSequence elements joined together with a copy of the specified delimiter. For example:

 Set<String> strings = new LinkedHashSet<>();
 strings.add("Java"); strings.add("is");
 strings.add("very"); strings.add("cool");
 String message = String.join("-", strings);
 //message returned is: "Java-is-very-cool"

Set implements Iterable, so simply use:

String.join(" ", set_1);
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
22

As a counterpoint to Seanizer's commons-lang answer, if you're using Google's Guava Libraries (which I'd consider the 'successor' to commons-lang, in many ways), you'd use Joiner:

Joiner.on(" ").join(set_1);

with the advantage of a few helper methods to do things like:

Joiner.on(" ").skipNulls().join(set_1);
// If 2nd item was null, would produce "1, 3"

or

Joiner.on(" ").useForNull("<unknown>").join(set_1);
// If 2nd item was null, would produce "1, <unknown>, 3"

It also has support for appending direct to StringBuilders and Writers, and other such niceties.

Cowan
  • 37,227
  • 11
  • 66
  • 65
  • What is the difference betweend guava-libraries and google collection library? – Shervin Asgari Jun 15 '10 at 10:04
  • the main difference is that guava knows about generics and commons/collections doesn't, but apart from that: they are two different libraries written by two different teams that solve some similar problems (and some non-similar ones) using different approaches – Sean Patrick Floyd Jun 15 '10 at 11:39
  • 2
    @seanizer, Shervin was asking about guava vs google-collections, not guava vs commons :) Shervin - guava is simply the replacement for google-collections. As the project increased in scope, it stopped being limited to collections-only things so a name change was in order. google-collections should basically be considered deprecated, guava is a drop-in replacement with bug fixes + more features. – Cowan Jun 15 '10 at 21:02
8

Maybe a shorter solution:

public String test78 (Set<String> set) {
    return set
        .stream()
        .collect(Collectors.joining(" "));
}

or

public String test77 (Set<String> set) {
    return set
        .stream()
        .reduce("", (a,b)->(a + " " + b));
}

but native, definitely faster

public String test76 (Set<String> set) {
    return String.join(" ", set);
}
7

I don't have the StringUtil library available (I have no choice over that) so using standard Java I came up with this ..

If you're confident that your set data won't include any commas or square brackets, you could use:

mySet.toString().replaceAll("\\[|\\]","").replaceAll(","," ");

A set of "a", "b", "c" converts via .toString() to string "[a,b,c]".

Then replace the extra punctuation as necesary.

Filth.

user2808054
  • 1,376
  • 1
  • 11
  • 19
4

I use this method:

public static String join(Set<String> set, String sep) {
    String result = null;
    if(set != null) {
        StringBuilder sb = new StringBuilder();
        Iterator<String> it = set.iterator();
        if(it.hasNext()) {
            sb.append(it.next());
        }
        while(it.hasNext()) {
            sb.append(sep).append(it.next());
        }
        result = sb.toString();
    }
    return result;
}
tuxdna
  • 8,257
  • 4
  • 43
  • 61
2

I'm confused about the code replication, why not factor it into a function that takes one set and returns one string?

Other than that, I'm not sure that there is much that you can do, except maybe giving the stringbuilder a hint about the expected capacity (if you can calculate it based on set size and reasonable expectation of string length).

There are library functions for this as well, but I doubt they're significantly more efficient.

Uri
  • 88,451
  • 51
  • 221
  • 321
  • Thanks for your answer! I didn't put it into a separate function to highlight the fact that I am trying to reuse the 'builder'-variable. Maybe that doesn't matter? – Lars Andren Jun 15 '10 at 01:52
  • 3
    It doesn't matter. Repurposing variables can actually get you into trouble down the line. Instead, just have variables go out of scope when they're no longer needed. – Gunslinger47 Jun 15 '10 at 02:07
  • @Gunslinger, I see, thanks! @Uri, thanks for the initial capacity tips. – Lars Andren Jun 15 '10 at 02:08
  • 1
    @Lars, there are cases where reuse makes sense but in others it is better to dump and start from scratch. I'm not sure what's better here. You could write a utility class that has an instance variable builder that is shared. One advantage for one-builder-per-run is that you could convert a large number of sets in parallel with multiple threads. – Uri Jun 15 '10 at 02:13
2

This can be done by creating a stream out of the set and then combine the elements using a reduce operation as shown below (for more details about Java 8 streams check here):

Optional<String> joinedString = set1.stream().reduce(new 
BinaryOperator<String>() {

     @Override
     public String apply(String t, String u) {

       return t + " " + u;
    }
});
return joinedString.orElse("");
Ehab Qadah
  • 570
  • 4
  • 12