I want to join strings together, but when doing this it often happens that there is a comma too many, and therefore I need to remove that comma. In this code, I use the substring
to delete the two last characters.
How can this become more elegant?
List<String> paramList = new ArrayList<String>( );
paramList.add( "param1" );
paramList.add( "param2" );
StringBuilder result = new StringBuilder();
for ( String p : paramList )
{
result.append( p ).append( ", " );
}
String withoutLastComma = result.substring( 0, result.length( ) - ", ".length( ) );
System.err.println( withoutLastComma );
15 Answers 15
One may use string utility methods such as StringUtil.join
to concatenate elements in an array or a collection object. Consult the StringUtil API's StringUtil.join
entry.
For example:
StringUtils.join(["a", "b", "c"], "--") // => "a--b--c"
-
46\$\begingroup\$ I don't see how something this simple can justify adding a library. \$\endgroup\$Athas– Athas2011年04月24日 23:04:11 +00:00Commented Apr 24, 2011 at 23:04
-
8\$\begingroup\$ @Wes: If you are working with critical production code and do not want to take on unnecessary dependencies (or have a policy requirement not to use external libraries) and have the time to write/test/debug/optimize your own methods, then by all means, roll your own. I made the assumption that OP is looking for a simple solution for a non-critical task. \$\endgroup\$Adeel Zafar Soomro– Adeel Zafar Soomro2011年04月25日 13:04:06 +00:00Commented Apr 25, 2011 at 13:04
-
2\$\begingroup\$ Guava has
Joiner
:Joiner.on(", ").join(paramList)
. It provides helpful options such as skippingnull
values, too. Oops, already an answer. \$\endgroup\$David Harkness– David Harkness2014年05月18日 21:00:54 +00:00Commented May 18, 2014 at 21:00 -
25\$\begingroup\$ Java 8 now also has a
String.join()
method, see my answer below. \$\endgroup\$Kolargol00– Kolargol002014年07月31日 03:18:33 +00:00Commented Jul 31, 2014 at 3:18 -
2\$\begingroup\$ @Athas: By repeating that statement for every new utility method you end up with a self made copy of all the methods of that library. And I don't even ask for the unit tests that you wrote for "something this simple". \$\endgroup\$Bananeweizen– Bananeweizen2016年11月13日 18:21:09 +00:00Commented Nov 13, 2016 at 18:21
for ( String p : paramList )
{
if (result.length() > 0) result.append( ", " );
result.append( p );
}
-
\$\begingroup\$ I've always used this method. It's clear. It looks nearly the same in any language. I've never seen where the extra comparison would ever have enough weight per iteration to make a difference. \$\endgroup\$hometoast– hometoast2011年04月19日 12:44:29 +00:00Commented Apr 19, 2011 at 12:44
-
3\$\begingroup\$ I used this solution in .NET, until I discovered that
string
has a staticJoin
method. I'm surprised Java doesn't have something similar built-in. \$\endgroup\$Kyralessa– Kyralessa2011年05月20日 03:40:20 +00:00Commented May 20, 2011 at 3:40 -
\$\begingroup\$ I like how this answer actually shows how to solve the problem, not just avoid it by using a library method. Not that using the library method is wrong though, its just you still don't see how it is actually solved. \$\endgroup\$Andrew Hagner– Andrew Hagner2013年03月20日 17:45:37 +00:00Commented Mar 20, 2013 at 17:45
-
\$\begingroup\$ It is exactly what StringUtils.join seems to be doing. \$\endgroup\$would_like_to_be_anon– would_like_to_be_anon2014年07月22日 16:49:33 +00:00Commented Jul 22, 2014 at 16:49
Java 8 provides a String.join()
method, so you can do it without depending on an external library.
List<String> paramList = new ArrayList<String>();
paramList.add("param1");
paramList.add("param2");
String withoutLastComma = String.join(", ", paramList);
-
\$\begingroup\$ This answer is being discussed on meta \$\endgroup\$rolfl– rolfl2014年07月31日 03:40:25 +00:00Commented Jul 31, 2014 at 3:40
-
\$\begingroup\$ Thanks for the explanation @rolfl. Sorry, I didn't notice the question was so old. :( \$\endgroup\$Kolargol00– Kolargol002014年07月31日 09:09:05 +00:00Commented Jul 31, 2014 at 9:09
-
1\$\begingroup\$ On a realated note you can use
Collectors.joining()
as well. \$\endgroup\$Vogel612– Vogel6122015年10月20日 15:18:58 +00:00Commented Oct 20, 2015 at 15:18 -
\$\begingroup\$ There was a proposed edit for this answer which was rejected but could be a viable solution - it appears to have the same output \$\endgroup\$2020年06月03日 16:31:38 +00:00Commented Jun 3, 2020 at 16:31
-
\$\begingroup\$ You can drop the type argument on the right side of assignment:
new ArrayList<String>()
->new ArrayList<>()
, it is not mandatory since Java 7 \$\endgroup\$Alexander Ivanchenko– Alexander Ivanchenko2024年04月17日 08:40:46 +00:00Commented Apr 17, 2024 at 8:40
I believe it's better to know how to write it and then use a library. I usually prefer to make a check before the loop, thus avoiding to have to check every time in the loop:
int size = paramList.size();
if (size > 0) {
result.append(paramList.get(0));
for (int i = 1; i < size; ++i) {
result.append(", ").append(paramList.get(i));
}
}
-
2\$\begingroup\$ Now you have two lines where you append your items to your result, instead of one. This is bad style. Don't duplicate lines for the sake of easiness. It will make it harder to maintain this code properly and faultlessly. \$\endgroup\$klaar– klaar2016年12月05日 10:54:38 +00:00Commented Dec 5, 2016 at 10:54
One of possible ways is a using Joiner from Google Guava library:
result = Joiner.on(", ").join(paramList);
That's strange that nobody has mentioned iterator-wise approach so far.
So here it goes:
public static <E> String join(Iterable<E> iterable, String delim) {
Iterator<E> iterator = iterable.iterator();
if (!iterator.hasNext()) {
return "";
}
StringBuilder builder = new StringBuilder(iterator.next().toString());
while (iterator.hasNext()) {
builder.append(delim).append(iterator.next().toString());
}
return builder.toString();
}
No messing with indexes, substringing, etc, etc.
And let's use it:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.println(join(list, ", "));
Update:
NPE-safe approach would be avoid using toString()
on next()
(thanks @David Harkness):
public static <E> String join(Iterable<E> iterable, String delim) {
Iterator<E> iterator = iterable.iterator();
if (!iterator.hasNext()) {
return "";
}
StringBuilder builder = new StringBuilder(iterator.next());
while (iterator.hasNext()) {
builder.append(delim).append(iterator.next());
}
return builder.toString();
}
-
5\$\begingroup\$ There's no need for the
toString()
calls on the elements sinceStringBuilder
does that automatically. Plus you're risking an NPE. \$\endgroup\$David Harkness– David Harkness2014年05月18日 21:10:06 +00:00Commented May 18, 2014 at 21:10
What I use is a variable that I initialize as empty and then set inside the loop.
List<String> paramList = new ArrayList<String>( );
paramList.add("param1");
paramList.add("param2");
String separator = "";
StringBuilder result = new StringBuilder();
for (String p : paramList)
{
result.append(separator)
result.append(p);
separator = ", ";
}
System.err.println(result.toString());
-
\$\begingroup\$ I often use this for languages without "join" functions, like PL/SQL... \$\endgroup\$Anthony Simmon– Anthony Simmon2013年12月04日 15:16:21 +00:00Commented Dec 4, 2013 at 15:16
A couple of alternate options here - any of such can simply only resolve to making the intentions clear, although it's hardly an issue.
It might be a little clearer, albeit not much, using lastIndexOf
:
String withoutLastComma = result.substring(0, result.lastIndexOf(","));
Or just refactor a little, which could be more explanatory:
StringBuilder result = new StringBuilder();
for (int i = 0; i < paramList.size(); i++)
{
result.append(paramList.get(i));
if (i + 1 != paramList.size())
result.append(", ");
}
System.err.println(result);
Or, lastly, use a string utility library as linked in other answers provided; though, the thought of doing so brings the term 'sledgehammer to crack a nut' to mind, but it might well be justified depending on what other operations you require to carry out.
-
1\$\begingroup\$ This makes the code more complex. Replacing the foreach with a for statement makes it a bit more complex. However your solution works and is correct, but you have to make sure not bringing in extra complexity. \$\endgroup\$Gertjan– Gertjan2011年04月19日 12:23:25 +00:00Commented Apr 19, 2011 at 12:23
-
\$\begingroup\$ I would argue that complexity in such a trivial construct is far, far from being reached (though understand where you're coming from in terms of 'more complex') - it is clear and self-explanatory for the everyday / professional programmer (IMO). Even just the following of the original warrants such changes, I think:
( 0, result.length( ) - ", ".length( ) );
And, thanks for the input. :) \$\endgroup\$Grant Thomas– Grant Thomas2011年04月19日 12:27:04 +00:00Commented Apr 19, 2011 at 12:27 -
\$\begingroup\$ for this sample you are right. But imagine a scenario with a couple of nested loops. In that case you get something like result[i].result[j].result[k]. In those cases a foreach might be a more readable solution because it is easy to make a small mistake like using 1 instead of i or mixup the i,j & k variable. \$\endgroup\$Gertjan– Gertjan2011年04月20日 08:08:30 +00:00Commented Apr 20, 2011 at 8:08
-
\$\begingroup\$ @Gertjan: I agree, luckily we're not in that situation here. ;) \$\endgroup\$Grant Thomas– Grant Thomas2011年04月20日 08:36:15 +00:00Commented Apr 20, 2011 at 8:36
I like this technique:
private String join(Iterable<?> items, String sep) {
Iterator<?> iter = items.iterator();
if (!iter.hasNext()) {
return "";
}
StringBuilder builder = new StringBuilder();
builder.append(iter.next());
while (iter.hasNext()) {
builder.append(sep).append(iter.next());
}
return builder.toString();
}
What I like about it is there is no wasted if
condition inside the loop.
Here are some unit tests to go with it:
@Test
public void testEmptyCollection() {
Assert.assertTrue(join(Collections.emptyList(), ", ").isEmpty());
}
@Test
public void testJoinSingleItem() {
String item = "hello";
Assert.assertEquals(item, join(Collections.singletonList(item), ", "));
}
@Test
public void testJoinTwoItems() {
Integer item1 = 4;
Integer item2 = 9;
String sep = ", ";
String expected = item1 + sep + item2;
Assert.assertEquals(expected, join(Arrays.asList(item1, item2), sep));
}
-
1\$\begingroup\$ Then you'll love Alexey's answer above. ;) \$\endgroup\$David Harkness– David Harkness2014年05月18日 21:12:52 +00:00Commented May 18, 2014 at 21:12
-
1\$\begingroup\$ Ah, too many answers, I overlooked that one. Normally I would have just left some suggestions in comments, instead of a full-blown answer. Too late now, I'll just keep this anyway, for the few small extras I added. \$\endgroup\$janos– janos2014年05月18日 21:46:05 +00:00Commented May 18, 2014 at 21:46
String listString = Arrays.toString(paramList.toArray());
System.err.println( listString );
Will return:
[param1, param2]
This has the added benefit of using the String.valueOf(object) method which will print 'null' in the case of null objects. The Arrays.toString() method is also pretty straight forward if you just want to reimplement it. Removing the brackets:
int iMax = paramList.size() - 1;
if (iMax == -1) {
return "";
}
String[] params = paramList.toArray();
StringBuilder b = new StringBuilder();
for (int i = 0; ; i++) {
String param = params[i];
b.append(param);
if (i == iMax) {
return b.toString();
}
b.append(", ");
}
Surprised no one has contributed a unit test specification:
- a useful result should contain
max(0, paramList.length() - 1)
commas. - a robust solution should not throw
IndexOutOfBoundsException
if the list is empty. - an efficient solution would provide a realistic
StringBuilder
capacity estimate.
The result can be misleading or useless if any parameter contains a comma. Java8 String.join
should be redesigned to flag this "delimiter collision" possibility at compile time, and only accept strings to be joined that can be split again afterwards because they have already been escaped or quoted or do not or cannot contain the delimiter.
-
\$\begingroup\$ There are a few problems with this (old) answer: 1. This would greatly decrease performance. 2. This is usually unnecessary. 3. "Can be split again" might rely on heuristics which can lead to random errors. 4. There is not necessarily a good way to fix the error flagged. \$\endgroup\$Solomon Ucko– Solomon Ucko2018年12月31日 19:26:26 +00:00Commented Dec 31, 2018 at 19:26
If you are using java 8, you can use StringJoiner like;
StringJoiner sj = new StringJoiner(",");
for ( String p : paramList )
{
sj.add(p);
}
When we use stream we do have more flexibility, like
map
⟶ convert any array object to string
filter
⟶ remove when it is empty
join
⟶ add joining character
//Deduplicate the comma character in the input string
String[] splits = input.split("\\s*,\\s*");
return Arrays.stream(splits).filter(StringUtils::isNotBlank).collect(Collectors.joining(", "));
-
\$\begingroup\$ You can ditch the intermediate variable
splits
\$\endgroup\$Alexander Ivanchenko– Alexander Ivanchenko2024年04月17日 08:17:00 +00:00Commented Apr 17, 2024 at 8:17 -
\$\begingroup\$ "join ⟶ add joining character" - a small correction:
joining
expects a delimiter of typeCharSequence
(a super-type ofString
,StringBuilder
,CharBuffer
) \$\endgroup\$Alexander Ivanchenko– Alexander Ivanchenko2024年04月17日 08:21:59 +00:00Commented Apr 17, 2024 at 8:21
As an alternative of manually going through the loop to construct the comma-separated contents of the List, you can take advantage of the List's toString()
method alongside with substring
method of String.
String contents = paramList.toString(); //returns [param 1, param2]
//remove `[` and `]`
System.out.println(contents.substring(1, contents.length()-1));
Here's a more efficient alternative to the delete
method, which just breaks the StringBuilder
without asking for chars or positions:
@Test
public void appendTest (){
final String comma = ", ";
List<String> paramList = new ArrayList<>();
paramList.add( "param1" );
paramList.add( "param2" );
StringBuilder result = new StringBuilder();
for (String s : paramList) {
result.append(s).append(comma);
}
if (!paramList.isEmpty()){
result.setLength(result.length() - comma.length());
}
System.out.println(result);
}
result.append(", ").append(p)
--allows you to use the simpler extraction:result.substring(", ".length())
\$\endgroup\$result.deleteCharAt(result.length)
which doesn't fit in here) \$\endgroup\$