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


98

在使用数据库的过程中,我注意到我编写了查询字符串,并且在此字符串中,我必须对列表/数组/集合的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查询)变得复杂时。

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


大概上面显示的SQL应该看起来像这样:从客户中选择* where(34,26,2)中的customer.id;
多纳尔

当列表项(字符串)本身包含逗号或双引号并且需要用引号将其转义时,这是一个棘手的部分。如果我没有错过任何内容,那么上面的示例将不考虑它,并且我讨厌遍历所有文本并搜索逗号的想法。您认为有解决此问题的更好方法吗?
武士女孩



Answers:


85

注意:这个答案在11年前写的时候是不错的,但是现在有更好的选择可以更清晰地在一行中执行此操作,无论是仅使用Java内置类还是使用实用程序库。请参阅下面的其他答案。


由于字符串是不可变的,因此如果要在代码中更改String,则可能需要使用StringBuilder类。

可以将StringBuilder类视为一个可变的String对象,当其内容更改时,该对象分配更多的内存。

通过处理多余的尾部逗号,可以更清楚,更高效地写出问题中的原始建议:

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

7
请注意,这要求您的集合至少要有一个元素。
Guus


请参阅建议的修复程序以获取空列表。
gimel 2012年

1
番石榴的答案更好。无需重新发明轮子。
davidjnelson 2012年

1
@ xtreme-biker使用相当现代的编译器,可以自动使用StringBuilder。在使用+ =之前检查您的环境。见stackoverflow.com/questions/1532461/...
吉梅尔


76

我只是看了今天执行此操作的代码。这是AviewAnew答案的变体。

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

我们使用 StringUtils(<-commons.lang 2.x,或commons.lang 3.x链接)来自Apache Commons


...以及StringUtils来自何处?
vwegert

1
啊,好点。自从我看过这段代码已有一段时间了,但是我相信我们正在使用org.apache.commons.lang.StringUtils。
Ogre Psalm10年

这是StringUtilscommons.apache.org/proper/commons-lang/javadocs/api-release/org/…的联接
Ryan S

2
很好,谢谢。StringUtils#join也可以在Iterable上使用,因此可能不需要先将集合转换为数组。
罗伊

47

我编写该循环的方式是:

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

不用担心sep的性能。作业非常快。无论如何,热点都倾向于剥离循环的第一次迭代(因为它经常必须处理诸如空值和单/双态内联检查之类的怪异事物)。

如果您多次使用它(不止一次),请使用共享方法。

还有一个关于stackoverflow的问题,涉及如何在SQL语句中插入ID列表。


42

从Java 8开始,您可以使用:


3
很好!如果要处理需要toString()未涵盖的特殊字符串转换的对象,请用java.util.function.Function <YourType,String>替换Object :: toString,该类将您的类映射到String。
Torben

2
同样,您可以像这样使用它:cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));对于集合中的单个变量。
numsu

我对此表示怀疑stream。对于int []或long []或其他可以将值简单地转换为的数组String,我会寻找一种非流式解决方案。实际上我正在寻找。
亚当

11

我发现迭代器习惯用法很优雅,因为它可以测试更多元素(为简洁起见,省略了null / empty测试):

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

...可能效率不如公认的解决方案,具体取决于“ hasNext()”调用的复杂程度。此外,您可能应该使用StringBuilder而不是String串联。
Stephen C

好吧,如果您想提高效率,请使用StringWriter;)
Miguel Ping

8

有很多手动解决方案,但是我想重申并更新上述朱莉的答案。使用google collections Joiner类

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

它处理var args,可迭代对象和数组,并正确处理多个字符的分隔符(与gimmel的答案不同)。如果需要,它还将处理列表中的空值。


7

这是我根据之前的建议构建的一个非常通用的版本:

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);
}

7
String.join(", ", collectionOfStrings)

在Java8 API中可用。

替代方法(无需添加Google番石榴依赖项):

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

5

你可以试试

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

4

这是到目前为止最短的解决方案,除了使用Guava或Apache Commons

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

很好,有0,1和n元素列表。但是您需要检查空列表。我在GWT中使用了它,所以在那里没有StringBuilder很好。对于仅包含几个元素的简短列表,其他地方也可以;)


4

万一最近有人迷路了,我使用Java 8添加了一个简单的变体reduce()。它还包括其他人已经提到的一些解决方案:

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, ","));

  }
}

4

在Android中,您应该使用以下命令:

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

4

我认为构造sql并置where子句值不是一个好主意,就像您在做的那样:

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

哪里valueX来自字符串列表。

首先,如果要比较字符串必须用引号引起来,那么如果字符串中可以有引号,则这并不容易。

其次,如果值来自用户或其他系统,则可能会发生SQL注入攻击。

冗长得多,但是您应该做的是创建一个像这样的String:

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

然后用绑定变量Statement.setString(nParameter,parameterValue)


3

只是解决此问题的另一种方法。不是最短的,但是它高效并且可以完成工作。

/**
 * 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();
}

2

有一些第三方Java库提供了字符串连接方法,但是您可能不想仅仅出于类似这样的事情而开始使用该库。我只是创建一个这样的帮助器方法,我认为它比您的版本好一点,它使用StringBuffer,如果您需要连接许多字符串,它将更加高效,并且可以处理任何类型的集合。

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();
}

使用Collection.toString()的另一种建议更短,但是它依赖于Collection.toString()返回非常特定格式的字符串,我个人不想依靠它。


2

如果使用Spring,则可以执行以下操作:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(软件包org.springframework.util)


1

我不确定这有多“复杂”,但肯定要短一些。它可以与各种不同类型的集合一起使用,例如Set <Integer>,List <String>等。

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) + ")";
}

读者练习:修改此方法,使其正确处理null / empty集合:)


1

使代码丑陋的是第一种情况的特殊处理。这个小片段中的大多数行都专用于执行代码的例行工作,而不是执行代码的例行工作。这就是gimel解决方案的替代方法,即通过将特殊处理移到循环之外。有一种特殊情况(嗯,您可以将开始和结束都视为特殊情况-但只有其中一种需要特殊处理),因此在循环内处理它会不必要地变得复杂。


1

我刚刚签入了我的图书馆美元考试:

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

它创建一个使用周围列表/数组/字符串的/ etc一口流利的包装只是一个静态导入$

注意

使用范围可以将以前的列表重写为 $(1, 5).join(",")


1

IN表达式的好处是,如果您有重复的值,则它不会更改结果。因此,只需复制第一项并处理整个列表。假定列表中至少有一项。如果没有项目,建议先检查一下,然后再完全不执行SQL。

这将达到目的,这很明显,并且不依赖任何外部库:

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

1

虽然我认为最好的选择是使用Guava的Joiner,但是如果我要手工编码,我会发现这种方法比“ first”标志或砍掉最后一个逗号更为优雅。

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();
}


1

另一种选择,基于我在这里看到的内容(稍作修改)。

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();
}

1

联接'方法'在Arrays中可用,而扩展的类AbstractCollections却不能覆盖toString()方法(实际上是中的所有集合java.util)。

例如:

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

这是非常奇怪的方式,因为它仅适用于数字,例如SQL。


1
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

来自springframeowrk的StringUtils:spring-core



0
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)

1
这是一个坏习惯。您不能假设toString实现会发生变化。
drindt

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

字符串表示形式包括一个集合元素的列表,这些元素按其迭代器返回的顺序排列,并括在方括号(“ []”)中。相邻元素由字符“,”(逗号和空格)分隔。

抽象集合javadoc


0

列出令牌=新的ArrayList(结果); 最终的StringBuilder构建器= 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();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.