64

Given a list

List<String> l = new ArrayList<String>();
l.add("one");
l.add("two");
l.add("three");

I have a method

String join(List<String> messages) {
        if (messages.isEmpty()) return "";
        if (messages.size() == 1) return messages.get(0);
        String message = "";
        message = StringUtils.join(messages.subList(0, messages.size() -2), ", ");
        message = message + (messages.size() > 2 ? ", " : "") + StringUtils.join(messages.subList(messages.size() -2, messages.size()), ", and ");
        return message;
    }

which, for l, produces "one, two, and three". My question is, is there a standard (apache-commons) method that does the same?, eg

WhatEverUtils.join(l, ", ", ", and ");

To clarify. My problem is not getting this method to work. It works just as I want it to, it's tested and all is well. My problem is that I could not find some apache-commons-like module which implements such functionality. Which surprises me, since I cannot be the first one to need this.

But then maybe everyone else has just done

StringUtils.join(l, ", ").replaceAll(lastCommaRegex, ", and");
philipxy
  • 14,867
  • 6
  • 39
  • 83
slipset
  • 2,960
  • 2
  • 21
  • 17
  • I don't think there's such an open source library, but I advise using a resource bundles since not all languages use the English word "and". – Oded Peer Nov 24 '11 at 12:04
  • @slipset, why don't you use some join from known library and just modify it a bit? Notice that "and" is English word, so if such library would exist, it had to have multi languages support – lukastymo Nov 24 '11 at 12:50
  • I might have been unclear, but I have written the method I show in my question, and it works just as I want. – slipset Nov 24 '11 at 13:14
  • There is a good answer for this here: [Join strings with different last delimiter](https://stackoverflow.com/a/34936891/411282) – Joshua Goldberg Apr 14 '23 at 20:46

10 Answers10

68

In Java 8 you can use String.join() like following:

Collection<String> elements = ....;
String result = String.join(", ", elements);
Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
15

What about join from: org.apache.commons.lang.StringUtils

Example:

StringUtils.join(new String[] { "one", "two", "three" }, ", "); // one, two, three

To have "and" or ", and" you can simple replace the last comma.

lukastymo
  • 26,145
  • 14
  • 53
  • 66
  • 3
    That's not producing `"one, two, and three"` as the OP requested. Observe: He's using that `join` method in his question – Lukas Eder Nov 24 '11 at 10:30
  • @Lukas, but you can replace the last comma, right? I don't see sense to have library which add for me this "and" in join method. – lukastymo Nov 24 '11 at 12:40
14

With Java 8, you can use streams with joiners.

Collection<String> strings;
...
String commaDelimited = strings.stream().collect(Collectors.joining(","));
// use strings.parallelStream() instead, if you think
//   there are gains to be had by doing fork/join
CppNoob
  • 2,322
  • 1
  • 24
  • 35
13

I like using Guava for this purpose. Neat and very useful:

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

This kind of code has been written time and time again and you should rather be freed implementing your specific implementation logic.

If you use maven, herewith the dependency:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.1-jre</version>
</dependency>

It has a bunch of other wonderful cool features too!

This will produce the string "one, two, and three".

List<String> originalList = Arrays.asList("one", "two", "three");
Joiner.on(", ")
    .join(originalList.subList(0, originalList.size() - 1))
    .concat(", and ")
    .concat(originalList.get(originalList.size() - 1));
Michael
  • 41,989
  • 11
  • 82
  • 128
Jaco Van Niekerk
  • 4,180
  • 2
  • 21
  • 48
8

To produce grammatical output in English there are 3 cases to consider when concatenating a list of strings:

  1. "A"

  2. "A and B"

  3. "A, B, and C.

This can be accomplished using standard Java or Guava like below. The solutions are basically the same and just up to preference what you want to use.

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;

import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;

public class JoinListTest {

    @Test
    public void test_join() {
        // create cases (don't need to use ImmutableList builder from guava)
        final List<String> case1 = new ImmutableList.Builder<String>().add("A").build();
        final List<String> case2 = new ImmutableList.Builder<String>().add("A", "B").build();
        final List<String> case3 = new ImmutableList.Builder<String>().add("A", "B", "C").build();
        // test with standard java
        assertEquals("A", joinListGrammaticallyWithJava(case1));
        assertEquals("A and B", joinListGrammaticallyWithJava(case2));
        assertEquals("A, B, and C", joinListGrammaticallyWithJava(case3));
        // test with guava
        assertEquals("A", joinListGrammaticallyWithGuava(case1));
        assertEquals("A and B", joinListGrammaticallyWithGuava(case2));
        assertEquals("A, B, and C", joinListGrammaticallyWithGuava(case3));
    }

    private String joinListGrammaticallyWithJava(final List<String> list) {
        return list.size() > 1
                ? String.join(", ", list.subList(0, list.size() - 1))
                    .concat(String.format("%s and ", list.size() > 2 ? "," : ""))
                    .concat(list.get(list.size() - 1))
                : list.get(0);
    }

    private String joinListGrammaticallyWithGuava(final List<String> list) {
        return list.size() > 1
                ? Joiner.on(", ").join(list.subList(0, list.size() - 1))
                    .concat(String.format("%s and ", list.size() > 2 ? "," : ""))
                    .concat(list.get(list.size() - 1))
                : list.get(0);
    }

}
Abtin Gramian
  • 1,630
  • 14
  • 13
  • 1
    @FerranNegre you are correct. The methods assume that a list of non-empty strings will be passed in. It would be simple enough to filter the list before passing it in or else added into these methods. Didn't warrant a downvote but sure. Ok. – Abtin Gramian Nov 29 '18 at 04:31
  • 1
    +1 - this is the answer that address the 'and' bit. The joiners are great but not the right tool for the question. – RankWeis May 31 '19 at 21:35
  • To use on lower Android API levels, you can use `TextUtils.join` instead of `String.join` – AtomicBoolean Aug 31 '20 at 16:25
6

Other answers talk about "replacing the last comma", which isn't safe in case the last term itself contains a comma.

Rather than use a library, you can just use one (albeit long) line of JDK code:

public static String join(List<String> msgs) {
    return msgs == null || msgs.size() == 0 ? "" : msgs.size() == 1 ? msgs.get(0) : msgs.subList(0, msgs.size() - 1).toString().replaceAll("^.|.$", "") + " and " + msgs.get(msgs.size() - 1);
}

See a live demo of this code handling all edge cases.


FYI, here's a more readable two-liner:

public static String join(List<String> msgs) {
    int size = msgs == null ? 0 : msgs.size();
    return size == 0 ? "" : size == 1 ? msgs.get(0) : msgs.subList(0, --size).toString().replaceAll("^.|.$", "") + " and " + msgs.get(size);
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
1

Improved version from Bohemian♦'s answer. You can choose to remove the nulled items check on personal preferences.

/** Auto Concat Wrapper
 *  Wraps a list of string with comma and concat the last element with "and" string.
 *  E.g: List["A", "B", "C", "D"] -> Output: "A, B, C and D"
 * @param elements
 */
public static String join(List<String> elements){
    if(elements==null){return "";}
    List<String> tmp = new ArrayList<>(elements);
    tmp.removeAll(Collections.singleton(null)); //Remove all nulled items

    int size = tmp.size();
    return size == 0 ? "" : size == 1 ? tmp.get(0) : String.join(", ", tmp.subList(0, --size)).concat(" and ").concat(tmp.get(size));
}

Test results:

List<String> w = Arrays.asList("A");
List<String> x = Arrays.asList("A", "B");
List<String> y = Arrays.asList("A", "B", null, "C");
List<String> z = Arrays.asList("A", "B", "C", "D");
System.out.println(join(w));//A
System.out.println(join(x));//A and B
System.out.println(join(y));//A, B and C
System.out.println(join(z));//A, B, C and D
1

I don't know any Apache String joiner that can support adding and in the joined String.

Here's an untested code that will do what you asked:

public static String join(String separator, List<String> mList, boolean includeAndInText) {
    StringBuilder sb = new StringBuilder();
    int count = 0;

    for (String m: mList) {
        if (includeAndInText && (count + 1 != mList.size())) {
            sb.append (" and ");
        }

        sb.append(m);
        count++;
        if (count < mList.size()) {
            sp.append(separator);
        }       
    }

    return sb.toString();
}
Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
  • sorry, but I don't think this is the best approach to this problem. IMO we should use something which exists (e.g. join from Apache) and modify it a bit to get " and ", " or " or something what we expect to have. – lukastymo Nov 24 '11 at 12:48
  • 1
    @smas, ok, I disagree. Changing an existing library to conform to your **only** requirement is not an obvious choice since (and I don't believe) you will commit your change to Apache. Plus, if another version of `StringUtils` gets released, you will have to maintain it and make sure that your change isn't affected (deleted). I say, leave the library implementation AS-IS and write your own Utility class. – Buhake Sindi Nov 24 '11 at 13:23
  • 1
    I diagree also. In business world well-known libraries like StringUtils are used very often to avoid reinventing the wheel. If you aware of some changes, you write unit tests. For this case writing join on your own only because "and" must be there is waste of time, money and Object Oriented Programming style. – lukastymo Nov 24 '11 at 14:25
0

If you want a more comprehensive solution, there is a brilliant NLG library for that - SimpleNLG

//initialize 
NLGFactory nlgFactory = new NLGFactory(Lexicon.getDefaultLexicon());
Realiser realiser = new Realiser(lexicon);
CoordinatedPhraseElement cp = nlgFactory.createCoordinatedPhrase();
cp.setConjunction("and");


//code begins here
List<String> l = new ArrayList<String>();
l.add("one");
l.add("two");
l.add("three");
l.forEach(cp::addCoordinate);


//output
String output = realiser.realise(cp).toString();

This can support any number of array elements without needing to do ugly hacks like "remove last comma".

SatheeshJM
  • 3,575
  • 8
  • 37
  • 60
0

In this way we can join

List<Long> ids = new ArrayList<>();
String idsAsString = String.join(",", ids);
System.out.println(idsAsString);
Rajim
  • 949
  • 6
  • 4