Joining a List<String> in Java with commas and "and"
JavaJoinApache CommonsJava Problem Overview
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");
Java Solutions
Solution 1 - Java
In Java 8 you can use String.join()
like following:
Collection<String> elements = ....;
String result = String.join(", ", elements);
Solution 2 - Java
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));
Solution 3 - Java
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.
Solution 4 - Java
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
Solution 5 - Java
To produce grammatical output in English there are 3 cases to consider when concatenating a list of strings:
-
"A"
-
"A and B"
-
"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);
}
}
Solution 6 - Java
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);
}
Solution 7 - Java
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();
}
Solution 8 - Java
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