views:

1264

answers:

11

Javascript has Array.join()

js>["Bill","Bob","Steve"].join(" and ")
Bill and Bob and Steve

Does Java have anything like this? I know I can cobble something up myself with StringBuilder:

static public String join(List<String> list, String conjunction)
{
   StringBuilder sb = new StringBuilder();
   boolean first = true;
   for (String item : list)
   {
      if (first)
         first = false;
      else
         sb.append(conjunction);
      sb.append(item);
   }
   return sb.toString();
}

...but there's no point in doing this if something like it is already part of the JDK.

+11  A: 

No, there's no such convenience method in the standard Java API.

Not surprisingly, Apache Commons provides such a thing in their StringUtils class in case you don't want to write it yourself.

Bart Kiers
It's always bugged me that String has a split but not a join. In a way it makes sense that it does, but it's just annoying that there isn't at least a static method for it in String.
R. Bemrose
I'm with you Bemrose. At least they gave us an `isEmpty()` method instead of a (static) `join()` method in `String`... :rollseyes: :)
Bart Kiers
A: 

You can do this:

String aToString = java.util.Arrays.toString(anArray);
// Do not need to do this if you are OK with '[' and ']'
aToString = aToString.substring(1, aToString.length() - 1);

Or a one-liner (only when you do not want '[' and ']')

String aToString = java.util.Arrays.toString(anArray).substring(1).replaceAll("\\]$", "");

Hope this helps.

NawaMan
This will not append the conjunction of choice.
hexium
OOPS!! I am sorry, I didn't spot that.
NawaMan
A bit .. nasty.
BalusC
+1  A: 

You might want to try Apache Commons StringUtils join method:

http://commons.apache.org/lang/api/org/apache/commons/lang/StringUtils.html#join%28java.util.Iterator, java.lang.String)

I've found that Apache StringUtils picks up jdk's slack ;-)

Dave Paroulek
+1  A: 

Code you have is right way to do it if you want to do using JDK without any external libraries. There is no simple "one-liner" that you could use in JDK.

If you can use external libs, I recommend that you look into org.apache.commons.lang.StringUtils class in Apache Commons library.

An example of usage:

List<String> list = Arrays.asList("Bill", "Bob", "Steve");
String joinedResult = StringUtils.join(list, " and ");
Juha Syrjälä
+2  A: 

Not out of the box, but many libraries have similar:

Commons Lang:

org.apache.commons.lang.StringUtils.join(list, conjunction);

Spring:

org.springframework.util.StringUtils.collectionToDelimitedString(list, conjunction);
Arne Burmeister
A: 

Google's Guava API also has .join(), although (as should be obvious with the other replies), Apache Commons is pretty much the standard here.

Dean J
A: 

EDIT

I also notice the toString() underlying implementation issue, and about the element containing the separator but I thought I was being paranoid.

Since I've got two comments on that regard, I'm changing my answer to:

    static String join( List<String> list , String replacement  ) {
        StringBuilder b = new StringBuilder();
        for( String item: list ) { 
            b.append( replacement ).append( item );
        }
        return b.toString().substring( replacement.length() );
    }

Which looks pretty similar to the original question.

So if you don't feel like adding the whole jar to your project you may use this.

I think there's nothing wrong with your original code. Actually, the alternative that everyone's is suggesting looks almost the same ( although it does a number of additional validations )

Here it is, along with the Apache 2.0 license.

public static String join(Iterator iterator, String separator) {
       // handle null, zero and one elements before building a buffer
       if (iterator == null) {
           return null;
       }
       if (!iterator.hasNext()) {
           return EMPTY;
       }
       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();
   }

Now we know, thank you open source

OscarRyz
This will work now, but you can't be sure how List.toString() will behave in the future. I would never trust a toString() operation other than printing some informational string about the object in question.In your defense, you're not the only one that proposes this solution.
Hans Doggen
What if the contents of an element in the list contains the substring `", "`?
Bart Kiers
OscarRyz
A: 

I put this in the category of functions of "so easy to write I don't bother to look for a library implementation". I can probably write this faster than I can do a Google search for an existing implementation, and then if I need some variation, I have the source, I understand it, etc.

In many projects I've created a class like this:

public class Joiner
{
  String prefix;
  String conj;
  String use;
  StringBuilder sb;
  public Joiner(String prefix, String conj)
  {
    prefix=this.prefix;
    conj=this.conj;

    use=prefix;
    sb=new StringBuilder();
  }
  public Joiner(String conj)
  {
    this("",conj);
  }
  public Joiner append(String s)
  {
    sb.append(use).append(s);
    use=conj;
  }
  public boolean isEmpty()
  {
    return sb.length()==0;
  }
  public String toString()
  {
    return sb.toString();
  }
}

Note this allows a prefix to be placed in front of the first item and then some other conjunction used after that.

This has many uses. Like, to make a comma-separated list:

Joiner commaList=new Joiner(","); commaList.append("Item1").append("Item2").append("Item3");

Or, to build a SQL WHERE clause with conditions ANDed together:

Joiner where=new Joiner(" where ", " and "); ... bunch of code to find value for foo ... where.append("foo="+quote(foo)); ... bunch of code to find value for bar ... where.append("bar="+quote(bar)); ... etc ...

(I'm assuming the "quote" function puts quotes around strings and escapes any embedded quotes. You don't stick a user input into a SQL string build without making sure to escape embedded quotes, do you?)

The prefix is only added if we have at least one entry, which works nicely in many contexts where we string this thing in with other stuff.

=====

Edit: Addendum:

Hmm, I've been downvoted at least twice for suggesting that one write simple routines yourself instead of searching the Internet for code written by someone else. How curious. I would think there's an obvious trade-off here.

The obvious advantage of getting some open source library is that someone else has already done all the work and hopefully tested and debugged it.

But open source is not really free. Searching for open source, downloading it, figuring out how to use it, and verifying that it meets your needs surely takes at least a few hours. Depending on the complexity of the requirement, possibly days or weeks. The code may or may not be easy to customize -- and as it's written by someone else, at best you'll have to figure it out. And while one might hope that it has been tested and debugged, it may not be.

If I know that it would take me significantly longer to write some code I need than to find it pre-written, sure I'm going to look for open source or a commercial package. It would take extraordinarily unusual requirements to justify writing my own database engine or inventing a new programming language.

But if I need a modest function to concatenate two strings or whatever, something that I know I can write myself in half an hour and test as part of testing the bigger program, I just do it. I can probably write it myself faster than I could find it written by someone else, and when I do, I know that it will do exactly what I want and not have to worry about undesired behavior in special cases.

Jay
When you write your own code instead of using proven libraries, I'm sure you also sit down and write all the unit tests?
Kevin Bourrillion
When the function is trivial like this, testing it is not a big deal. How many test cases would you come up with for this? I'm not saying that I would write my own database engine on a whim. But when I need to add two numbers together, I don't search for a library to do it. I just write the one line of code. Do you really search for an existing solution for EVERYTHING? And then how do you tie them all together? Do you write tests for all the mortar you need? What if you need more code to tie canned solutions together than you would have had to write just to solve the original problem?
Jay
+12  A: 

All the references to Apache Commons are fine (and that is what most people use) but I think the google-collections equivalent, Joiner, has a much nicer API.

You can do the simple join case with

Joiner.on(" and ").join(names)

but also easily deal with nulls:

Joiner.on(" and ").skipNulls().join(names);

or

Joiner.on(" and ").useForNull("[unknown]").join(names);

and (useful enough as far as I'm concerned to use it in preference to commons-lang), the ability to deal with Maps:

Map<String, Integer> ages = .....;
String foo = Joiner.on(", ").withKeyValueSeparator(" is ").join(ages);
// Outputs:
// Bill is 25, Joe is 30, Betty is 35

which is extremely useful for debugging etc.

Cowan
+1 for a well-thought API.
BalusC
interesting... thanks for posting + great examples.
Jason S
thanks for the plug! Our Joiner also gives you the option to append directly to an Appendable (such as a StringBuilder, or any Writer) without creating intermediate Strings, which the Apache library seems to lack.
Kevin Bourrillion
A: 

using Dollar is simple as typing:

String joined = $(aList).join(" and ");

check also this answer for the gory details

dfa
er ummm... I was going to say you have the wrong language (Jquery?) but in taking a look at this "Dollar" API I'm a little nervous using the $ character in Java as I thought it's one of those reserved characters for the compiler to use with nested classes.
Jason S
heh, the API has only static methods named "$". Using $ in identifiers is perfectly legal in Java, as specified in the language specs. If you have an inner class named "$" the class file should be `Outer$$.class`
dfa