从集合/数组/列表中创建逗号分隔的字符串的最复杂的方法是什么?

在使用数据库的过程中,我注意到我编写了查询字符串,在这个字符串中,我必须在 list/array/Collection 的 where 子句中加入一些限制。应该是这样的:

select * from customer
where customer.id in (34, 26, ..., 2);

您可以将这个问题简化为您拥有字符串集合并希望在一个字符串中创建一个以逗号分隔的字符串列表。

我到目前为止使用的方法大致如下:

String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
if(first) {
result+=string;
first=false;
} else {
result+=","+string;
}
}

但是你可以看到,这是非常丑陋的。您第一眼看不到那里发生了什么,特别是当构造的字符串(像每个 SQL 查询一样)变得复杂时。

你(更)优雅的方式是什么?

160150 次浏览

I'm not sure how "sophisticated" this is, but it's certainly a bit shorter. It will work with various different types of collection e.g. Set<Integer>, List<String>, etc.

public static final String toSqlList(Collection<?> values) {


String collectionString = values.toString();


// Convert the square brackets produced by Collection.toString() to round brackets used by SQL
return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}

Exercise for reader: modify this method so that it correctly handles a null/empty collection :)

There are some third-party Java libraries that provide string join method, but you probably don't want to start using a library just for something simple like that. I would just create a helper method like this, which I think is a bit better than your version, It uses StringBuffer, which will be more efficient if you need to join many strings, and it works on a collection of any type.

public static <T> String join(Collection<T> values)
{
StringBuffer ret = new StringBuffer();
for (T value : values)
{
if (ret.length() > 0) ret.append(",");
ret.append(value);
}
return ret.toString();
}

Another suggestion with using Collection.toString() is shorter, but that relies on Collection.toString() returning a string in a very specific format, which I would personally not want to rely on.

Note: This answers was good when it was written 11 years ago, but now there are far better options to do this more cleanly in a single line, both using only Java built-in classes or using a utility library. See other answers below.


Since strings are immutable, you may want to use the StringBuilder class if you're going to alter the String in the code.

The StringBuilder class can be seen as a mutable String object which allocates more memory when its content is altered.

The original suggestion in the question can be written even more clearly and efficiently, by taking care of the redundant trailing comma:

    StringBuilder result = new StringBuilder();
for(String string : collectionOfStrings) {
result.append(string);
result.append(",");
}
return result.length() > 0 ? result.substring(0, result.length() - 1): "";

I think it's not a good idea contruct the sql concatenating the where clause values like you are doing :

SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)

Where valueX comes from a list of Strings.

First, if you are comparing Strings they must be quoted, an this it isn't trivial if the Strings could have a quote inside.

Second, if the values comes from the user,or other system, then a SQL injection attack is possible.

It's a lot more verbose but what you should do is create a String like this:

SELECT.... FROM.... WHERE ID IN( ?, ?,....?)

and then bind the variables with Statement.setString(nParameter,parameterValue).

I just looked at code that did this today. This is a variation on AviewAnew's answer.

collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");

The StringUtils ( <-- commons.lang 2.x, or commons.lang 3.x link) we used is from Apache Commons.

The way I write that loop is:

StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
buff.append(sep);
buff.append(str);
sep = ",";
}
return buff.toString();

Don't worry about the performance of sep. An assignment is very fast. Hotspot tends to peel off the first iteration of a loop anyway (as it often has to deal with oddities such as null and mono/bimorphic inlining checks).

If you use it lots (more than once), put it in a shared method.

There is another question on stackoverflow dealing with how to insert a list of ids into an SQL statement.

Use the Google Guava API's join method:

Joiner.on(",").join(collectionOfStrings);

Just another method to deal with this problem. Not the most short, but it is efficient and gets the job done.

/**
* Creates a comma-separated list of values from given collection.
*
* @param <T> Value type.
* @param values Value collection.
* @return Comma-separated String of values.
*/
public <T> String toParameterList(Collection<T> values) {
if (values == null || values.isEmpty()) {
return ""; // Depending on how you want to deal with this case...
}
StringBuilder result = new StringBuilder();
Iterator<T> i = values.iterator();
result.append(i.next().toString());
while (i.hasNext()) {
result.append(",").append(i.next().toString());
}
return result.toString();
}

I found the iterator idiom elegant, because it has a test for more elements (ommited null/empty test for brevity):

public static String convert(List<String> list) {
String res = "";
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
res += iterator.next() + (iterator.hasNext() ? "," : "");
}
return res;
}

You may be able to use LINQ (to SQL), and you may be able to make use of the Dynamic Query LINQ sample from MS. http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

You could try

List collections = Arrays.asList(34, 26, "...", 2);
String asString = collection.toString();
// justValues = "34, 26, ..., 2"
String justValues = asString.substring(1, asString.length()-1);

What makes the code ugly is the special-handling for the first case. Most of the lines in this small snippet are devoted, not to doing the code's routine job, but to handling that special case. And that's what alternatives like gimel's solve, by moving the special handling outside the loop. There is one special case (well, you could see both start and end as special cases - but only one of them needs to be treated specially), so handling it inside the loop is unnecessarily complicated.

There's a lot of manual solutions to this, but I wanted to reiterate and update Julie's answer above. Use google collections Joiner class.

Joiner.on(", ").join(34, 26, ..., 2)

It handles var args, iterables and arrays and properly handles separators of more than one char (unlike gimmel's answer). It will also handle null values in your list if you need it to.

Join 'methods' are available in Arrays and the classes that extend AbstractCollections but doesn't override toString() method (like virtually all collections in java.util).

For instance:

String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there

That's quite weird way since it works only for numbers alike data SQL wise.

I've just checked-in a test for my library dollar:

@Test
public void join() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
String string = $(list).join(",");
}

it create a fluent wrapper around lists/arrays/strings/etc using only one static import: $.

NB:

using ranges the previous list can be re-writed as $(1, 5).join(",")

The nice thing about the IN expression is that if you have repeated values, it does not change the result. So, just duplicate the first item and process the entire list. This assumes that there is at least one item in the list. If there are no items, I'd suggest checking for that first and then not executing the SQL at all.

This will do the trick, is obvious in what it is doing and does not rely on any external libraries:

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
inString.append(",").append(currentID);
}

Here's an incredibly generic version that I've built from a combination of the previous suggestions:

public static <T> String buildCommaSeparatedString(Collection<T> values) {
if (values==null || values.isEmpty()) return "";
StringBuilder result = new StringBuilder();
for (T val : values) {
result.append(val);
result.append(",");
}
return result.substring(0, result.length() - 1);
}
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));


$~(Hola, Julio)

If you use Spring, you can do:

StringUtils.arrayToCommaDelimitedString(
collectionOfStrings.toArray()
)

(package org.springframework.util)

While I think your best bet is to use Joiner from Guava, if I were to code it by hand I find this approach more elegant that the 'first' flag or chopping the last comma off.

private String commas(Iterable<String> strings) {
StringBuilder buffer = new StringBuilder();
Iterator<String> it = strings.iterator();
if (it.hasNext()) {
buffer.append(it.next());
while (it.hasNext()) {
buffer.append(',');
buffer.append(it.next());
}
}


return buffer.toString();
}

This will be the shortest solution so far, except of using Guava or Apache Commons

String res = "";
for (String i : values) {
res += res.isEmpty() ? i : ","+i;
}

Good with 0,1 and n element list. But you'll need to check for null list. I use this in GWT, so I'm good without StringBuilder there. And for short lists with just couple of elements its ok too elsewhere ;)

if you have an array you can do:

Arrays.asList(parameters).toString()
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

The string representation consists of a list of the collection's elements in the order they are returned by its iterator, enclosed in square brackets ("[]"). Adjacent elements are separated by the characters ", " (comma and space).

AbstractCollection javadoc

List token=new ArrayList(result); final StringBuilder builder = new StringBuilder();

    for (int i =0; i < tokens.size(); i++){
builder.append(tokens.get(i));
if(i != tokens.size()-1){
builder.append(TOKEN_DELIMITER);
}
}

builder.toString();

String.join(", ", collectionOfStrings)

available in the Java8 api.

alternative to (without the need to add a google guava dependency):

Joiner.on(",").join(collectionOfStrings);

Another option, based on what I see here (with slight modifications).

public static String toString(int[] numbers) {
StringBuilder res = new StringBuilder();
for (int number : numbers) {
if (res.length() != 0) {
res.append(',');
}
res.append(number);
}
return res.toString();
}

In case someone stumbled over this in more recent times, I have added a simple variation using Java 8 reduce(). It also includes some of the already mentioned solutions by others:

import java.util.Arrays;
import java.util.List;


import org.apache.commons.lang.StringUtils;


import com.google.common.base.Joiner;


public class Dummy {
public static void main(String[] args) {


List<String> strings = Arrays.asList("abc", "de", "fg");
String commaSeparated = strings
.stream()
.reduce((s1, s2) -> {return s1 + "," + s2; })
.get();


System.out.println(commaSeparated);


System.out.println(Joiner.on(',').join(strings));


System.out.println(StringUtils.join(strings, ","));


}
}

In Android you should use this:

TextUtils.join(",",collectionOfStrings.toArray());

Since Java 8, you can use:

There is an easy way. You can get your result in a single line.

String memberIdsModifiedForQuery = memberIds.toString().replace("[", "(").replace("]", ")");

To get complete idea check the code below

 public static void main(String[] args) {
List<Integer>memberIds=new ArrayList<Integer>();  //This contain member ids we want to process
//adding some sample values for example
memberIds.add(3);
memberIds.add(4);
memberIds.add(2);
String memberIdsModifiedForQuery = memberIds.toString().replace("[", "(").replace("]", ")"); //here you will get (3,4,5) That you can directly use in query
System.out.println(memberIdsModifiedForQuery);
String exampleQuery="select * from customer where customer.id in "+memberIdsModifiedForQuery+" ";
}

The Most easier way in android for convert List to Comma separated String is By useing android.text.TextUtils

 ArrayList<String>Myli = new ArrayList<String>();
String ArayCommase=android.text.TextUtils.join(",", Myli);
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

StringUtils from springframeowrk:spring-core