Java中连接字符串集合的首选语法是什么?

22

给定一个字符串集合,如何在纯Java中将它们连接起来,而不使用外部库?

考虑以下变量:

Collection<String> data = Arrays.asList("Snap", "Crackle", "Pop");
String separator = ", ";
String joined; // let's create this, shall we?

以下是我在Guava中的做法:

joined = Joiner.on(separator).join(data);

而在 Apache Commons / Lang 中:

joined = StringUtils.join(data, separator);

但在普通的Java中,真的没有比这更好的方法吗?

StringBuilder sb = new StringBuilder();
for(String item : data){
    if(sb.length()>0)sb.append(separator);
    sb.append(item);
}
joined = sb.toString();

请参考以下链接:https://dev59.com/6E_Ta4cB1Zd3GeqPCJqi,https://dev59.com/dnI-5IYBdhLWcg3wpaB1,https://dev59.com/onI_5IYBdhLWcg3wHvU5。 - Sébastien Le Callonnec
@Sébastien 没有一个比我更短且不使用外部库的答案。 - Sean Patrick Floyd
在http://stackoverflow.com/questions/1751844/java-convert-liststring-to-a-joind-string上接受的答案回答了你的问题:不,除了使用第三方库之外,没有更好的方法来完成它。 - Joachim Sauer
3
没有简单而干净的方法来做这件事,这就解释了为什么必须将它添加到所有这些库中。 - Kevin Bourrillion
@Kevin,作为你这样的人应该知道 :-) - Sean Patrick Floyd
我在我的回答中添加了使用Java 8的方法。 - aioobe
7个回答

15

我认为最好的方法(如果你所说的“最好”不是指“最简洁”),而又不使用Guava的话,就是使用Guava内部使用的技术,对于你的示例,代码将类似于:

Iterator<String> iter = data.iterator();
StringBuilder sb = new StringBuilder();
if (iter.hasNext()) {
  sb.append(iter.next());
  while (iter.hasNext()) {
    sb.append(separator).append(iter.next());
  }
}
String joined = sb.toString();

在迭代过程中不需要进行布尔检查,也不需要对字符串进行任何后处理。


10

也许我可以稍微修改一下,在循环中不要反复调用sb.length()。

StringBuilder sb = new StringBuilder();
String separator = "";
for(String item : data){
    sb.append(separator);
    separator=",";
    sb.append(item);
}
joined = sb.toString();

1
为什么要修改?旧的基于字符串的版本更加灵活,而新版本在开头添加了不必要的空格。 - Sean Patrick Floyd
1
抱歉,过早的优化会带来很多问题 :) - Dead Programmer
这也是我做的方式。它避免了额外的if,而采用了额外的非条件语句,在Bob Martin的“转换优先原则”框架下更可取(http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html)。它也可能更快地执行(分支在处理器中有性能惩罚,而重复赋值很可能几乎没有成本;对于循环展开器来说,优化此形式中的额外语句也可能更容易)。 - Jules

8

Java 8+

joined =String.join(separator, data);

Java 7及以下版本

StringBuilder sb = new StringBuilder();
boolean first = true;
for(String item : data){
    if(!first || (first = false)) sb.append(separator);
    sb.append(item);
}
joined = sb.toString();

2
我不喜欢使用 if(first),因为它过于冗长,但我必须承认 if(!first || (first = false)) 很巧妙(+1)。 - Sean Patrick Floyd
我本来想给@Suresh S打勾,但我不喜欢当前的编辑,所以就算了。 - Sean Patrick Floyd
与其使用您的第一个示例,您可以尝试这样做:joined = data.toString().substring(1, joined.length() - 2); - Dan King
@DanKing,在为joined分配有意义的数据之前,您不能使用joined.length() - aioobe
非常正确 @aioobe - 我会准备离开了!;-)虽然我注意到你已经完全改变了你的答案,所以无论如何它都不再有意义了 - 如果一开始就是正确的话,但事实并非如此... - Dan King

5

在Java 8中:

String.join (separator, data)

1

收集器可以加入,如果你想要过滤或者映射你的数据,这非常有用。

String joined = Stream.of("Snap", "Crackle", "Pop")
        .map(String::toUpperCase)
        .collect(Collectors.joining(", "));

要从集合中获取一个流,请在其上调用 stream()
Arrays.asList("Snap", "Crackle", "Pop").stream()

1

不同的意图:

对于像Guava的Joiner这样的第三方工具,它并不快速,但非常灵活。有许多选项方法可以自定义连接行为,例如skipNulls()

纯Java是快速的,但需要您自己编写一些代码。


4
我认为Joiner已经足够快了,特别是如果你重复使用一个预先构建的Joiner来代表某个特定的分隔符。它使用StringBuilder进行构建,并且有一种确保只在正确位置添加分隔符的非常有趣的方式,而不需要在每次迭代中进行任何布尔检查。 - ColinD

0

你的建议是一个可能的解决方案,另一个常见的解决方案是:

for (String item: data) {
    sb.append(item)
    sb.append(separator);
}
String joined = sb.substring(0, sb.length() - separator.length())

更像是:sb.substring(0, sb.length() - separator.length())。但我认为这是一个糟糕的hack。 - Sean Patrick Floyd
2
事实上,你必须在使用黑客、经常无用的测试和使用迭代器来提取列表中的第一个成员之间进行选择。没有完美的解决方案。 - Nicolas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接