What's the best way to build a string of delimited items in Java?
在Java应用程序中工作时,我最近需要组装一个以逗号分隔的值列表,以传递给另一个Web服务,而无需事先知道有多少元素。 我能想出的最好的东西是这样的:
1 2 3 4 5 6 7 8 9 10 11
| public String appendWithDelimiter( String original, String addition, String delimiter ) {
if ( original.equals("" ) ) {
return addition;
} else {
return original + delimiter + addition;
}
}
String parameterString ="";
if ( condition ) parameterString = appendWithDelimiter( parameterString,"elementName","," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString,"anotherElementName","," ); |
我意识到这不是特别有效,因为在整个地方都会创建字符串,但我的目的是为了清晰而不是优化。
在Ruby中,我可以做这样的事情,感觉更优雅:
1 2 3 4
| parameterArray = [];
parameterArray <<"elementName" if condition;
parameterArray <<"anotherElementName" if anotherCondition;
parameterString = parameterArray.join(","); |
但由于Java缺少连接命令,我无法找出任何等效的东西。
那么,在Java中执行此操作的最佳方法是什么?
前Java 8:
Apache的commons lang是你的朋友 - 它提供了一个非常类似于你在Ruby中引用的连接方法:
StringUtils.join(java.lang.Iterable,char)
Java 8:
Java 8通过StringJoiner和String.join()提供开箱即用的连接。下面的代码段显示了如何使用它们:
StringJoiner
1 2 3
| StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); //"01,02,03" |
String.join(CharSequence delimiter, CharSequence... elements))
1
| String joinedString = String.join(" -","04","05","06"); //"04 - 05 - 06" |
String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
1 2 3 4 5
| List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join("", strings);
//message returned is:"Java is cool" |
您可以编写一个适用于java.util.Lists的小型连接样式实用程序方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static String join(List<String> list, String delim) {
StringBuilder sb = new StringBuilder();
String loopDelim ="";
for(String s : list) {
sb.append(loopDelim);
sb.append(s);
loopDelim = delim;
}
return sb.toString();
} |
然后像这样使用它:
1 2 3 4 5 6
| List<String> list = new ArrayList<String>();
if( condition ) list.add("elementName");
if( anotherCondition ) list.add("anotherElementName");
join(list,","); |
在Android的情况下,来自commons的StringUtils类不可用,所以为此我使用了
1
| android.text.TextUtils.join(CharSequence delimiter, Iterable tokens) |
http://developer.android.com/reference/android/text/TextUtils.html
Google的Guava库有com.google.common.base.Joiner类,有助于解决此类任务。
样品:
1 2 3 4 5 6 7 8 9 10 11
| "My pets are:" + Joiner.on(",").join(Arrays.asList("rabbit","parrot","dog"));
// returns"My pets are: rabbit, parrot, dog"
Joiner.on(" AND").join(Arrays.asList("field1=1" ,"field2=2","field3=3"));
// returns"field1=1 AND field2=2 AND field3=3"
Joiner.on(",").skipNulls().join(Arrays.asList("London","Moscow", null,"New York", null,"Paris"));
// returns"London,Moscow,New York,Paris"
Joiner.on(",").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona","FC Bayern", null, null,"Chelsea FC","AC Milan"));
// returns"FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan" |
这是一篇关于Guava的字符串实用程序的文章。
在Java 8中,您可以使用String.join():
1 2
| List<String> list = Arrays.asList("foo","bar","baz");
String joined = String.join(" and", list); //"foo and bar and baz" |
还要看一下Stream API示例的答案。
您可以对它进行概括,但正如您所说,Java中没有连接。
这可能会更好。
1 2 3 4 5 6 7
| public static String join(Iterable<? extends CharSequence> s, String delimiter) {
Iterator<? extends CharSequence> iter = s.iterator();
if (!iter.hasNext()) return"";
StringBuilder buffer = new StringBuilder(iter.next());
while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
return buffer.toString();
} |
在Java 8中你可以这样做:
1 2
| list.stream().map(Object::toString)
.collect(Collectors.joining(delimiter)); |
如果list有空值,你可以使用:
1 2
| list.stream().map(String::valueOf)
.collect(Collectors.joining(delimiter)) |
它还支持前缀和后缀:
1 2
| list.stream().map(String::valueOf)
.collect(Collectors.joining(delimiter, prefix, suffix)); |
使用基于java.lang.StringBuilder的方法! ("一个可变的字符序列。")
就像你提到的那样,所有这些字符串连接都在创建字符串。 StringBuilder不会这样做。
为什么StringBuilder而不是StringBuffer?来自StringBuilder javadoc:
Where possible, it is recommended that this class be used in preference to StringBuffer as it will be faster under most implementations.
我会使用Google Collections。有一个很好的加入设施。
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html
但如果我想自己写,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package util;
import java.util.ArrayList;
import java.util.Iterable;
import java.util.Collections;
import java.util.Iterator;
public class Utils {
// accept a collection of objects, since all objects have toString()
public static String join(String delimiter, Iterable<? extends Object> objs) {
if (objs.isEmpty()) {
return"";
}
Iterator<? extends Object> iter = objs.iterator();
StringBuilder buffer = new StringBuilder();
buffer.append(iter.next());
while (iter.hasNext()) {
buffer.append(delimiter).append(iter.next());
}
return buffer.toString();
}
// for convenience
public static String join(String delimiter, Object... objs) {
ArrayList<Object> list = new ArrayList<Object>();
Collections.addAll(list, objs);
return join(delimiter, list);
}
} |
我认为它对象集合更好用,因为现在你不必在加入它们之前将对象转换为字符串。
Apache commons StringUtils类有一个join方法。
Java 8
1
| stringCollection.stream().collect(Collectors.joining(",")); |
使用StringBuilder和类Separator
1 2 3 4 5
| StringBuilder buf = new StringBuilder();
Separator sep = new Separator(",");
for (String each : list) {
buf.append(sep).append(each);
} |
分隔符包装分隔符。分隔符由分隔符的toString方法返回,除非第一次调用返回空字符串!
类Separator的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Separator {
private boolean skipFirst;
private final String value;
public Separator() {
this(",");
}
public Separator(String value) {
this.value = value;
this.skipFirst = true;
}
public void reset() {
skipFirst = true;
}
public String toString() {
String sep = skipFirst ?"" : value;
skipFirst = false;
return sep;
}
} |
最小的一个(如果你不想仅仅为了加入字符串而将Apache Commons或Gauva包含在项目依赖项中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /**
*
* @param delim : String that should be kept in between the parts
* @param parts : parts that needs to be joined
* @return a String that's formed by joining the parts
*/
private static final String join(String delim, String... parts) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++) {
builder.append(parts[i]).append(delim);
}
if(parts.length > 0){
builder.append(parts[parts.length - 1]);
}
return builder.toString();
} |
您可以使用Java的StringBuilder类型。还有StringBuffer,但它包含额外的线程安全逻辑,这通常是不必要的。
如果您正在使用Eclipse集合,则可以使用makeString()或appendString()。
makeString()返回String表示,类似于toString()。
它有三种形式
-
makeString(start, separator, end)
-
makeString(separator)默认开始和结束为空字符串
-
makeString()默认分隔符为","(逗号和空格)
代码示例:
1 2 3 4 5
| MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[","/","]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[",",","]")); |
appendString()类似于makeString(),但它附加到Appendable(如StringBuilder)并且void。它具有相同的三种形式,附加的第一个参数是Appendable。
1 2 3 4
| MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable,"[","/","]");
assertEquals("[1/2/3]", appendable.toString()); |
如果无法将集合转换为Eclipse集合类型,则只需使用相关适配器进行调整即可。
1 2
| List<Object> list = ...;
ListAdapter.adapt(list).makeString(","); |
注意:我是Eclipse集合的提交者。
为什么不编写自己的join()方法?它将作为字符串的参数集合和分隔符字符串。在该方法中迭代集合并在StringBuffer中构建结果。
如果您使用的是Spring MVC,那么您可以尝试以下步骤。
1 2 3 4 5 6 7 8
| import org.springframework.util.StringUtils;
List<String> groupIds = new List<String>;
groupIds.add("a");
groupIds.add("b");
groupIds.add("c");
String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray()); |
它将导致a,b,c
你为什么不用Java在ruby中做同样的事情,那就是在你将所有的部分添加到数组之后创建分隔符分隔的字符串?
1 2 3 4 5 6 7 8 9 10
| ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep =""; StringBuffer b = new StringBuffer();
for (String p: parms) {
b.append(sep);
b.append(p);
sep ="yourDelimiter";
} |
您可能希望在单独的帮助器方法中移动for循环,并且还使用StringBuilder而不是StringBuffer ...
编辑:修正追加顺序。
您应该使用带有append方法的StringBuilder来构造结果,否则这就像Java必须提供的解决方案一样好。
使用Java 5变量args,因此您不必将所有字符串显式填充到集合或数组中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import junit.framework.Assert;
import org.junit.Test;
public class StringUtil
{
public static String join(String delim, String... strings)
{
StringBuilder builder = new StringBuilder();
if (strings != null)
{
for (String str : strings)
{
if (builder.length() > 0)
{
builder.append(delim).append("");
}
builder.append(str);
}
}
return builder.toString();
}
@Test
public void joinTest()
{
Assert.assertEquals("", StringUtil.join(",", null));
Assert.assertEquals("", StringUtil.join(",",""));
Assert.assertEquals("", StringUtil.join(",", new String[0]));
Assert.assertEquals("test", StringUtil.join(",","test"));
Assert.assertEquals("foo, bar", StringUtil.join(",","foo","bar"));
Assert.assertEquals("foo, bar, x", StringUtil.join(",","foo","bar","x"));
}
} |
对于那些处于Spring上下文中的人,他们的StringUtils类也很有用:
有许多有用的快捷方式,如:
-
collectionToCommaDelimitedString(Collection coll)
-
collectionToDelimitedString(Collection coll,String delim)
-
arrayToDelimitedString(Object [] arr,String delim)
和许多其他人。
如果您尚未使用Java 8并且已经处于Spring上下文中,这可能会有所帮助。
我更喜欢它反对Apache Commons(虽然非常好),对于Collection支持这样更容易:
1 2 3 4 5
| // Encoding Set<String> to String delimited
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes,";");
// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString); |
Java 8 Native Type
1 2 3 4 5 6
| List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(",")); |
Java 8自定义对象:
1 2 3
| List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(",")); |
你可以尝试这样的事情:
1 2 3 4
| StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString(); |
所以基本上是这样的:
1 2 3 4 5 6 7 8 9 10 11 12
| public static String appendWithDelimiter(String original, String addition, String delimiter) {
if (original.equals("")) {
return addition;
} else {
StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
sb.append(original);
sb.append(delimiter);
sb.append(addition);
return sb.toString();
}
} |
不知道这是否真的更好,但至少它使用的是StringBuilder,它可能稍微高效一点。
如果您可以在执行任何参数分隔之前构建参数列表,则下面是更通用的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| // Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
StringBuilder sb = new StringBuilder(original);
if(sb.length()!=0) {
sb.append(delimiter).append(addition);
} else {
sb.append(addition);
}
return sb.toString();
}
// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
StringBuilder sb = new StringBuilder();
for (String string : strings) {
if(sb.length()!=0) {
sb.append(delimiter).append(string);
} else {
sb.append(string);
}
}
return sb.toString();
}
public void testAppendWithDelimiters() {
String string = appendWithDelimitersGeneric(",","string1","string2","string3");
} |
你的方法也不错,但你应该使用StringBuffer而不是使用+号。 +有一个很大的缺点,即为每个操作创建一个新的String实例。字符串越长,开销就越大。所以使用StringBuffer应该是最快的方法:
1 2 3 4 5 6 7 8 9 10 11
| public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
if ( original == null ) {
StringBuffer buffer = new StringBuffer();
buffer.append(addition);
return buffer;
} else {
buffer.append(delimiter);
buffer.append(addition);
return original;
}
} |
完成字符串创建后,只需在返回的StringBuffer上调用toString()。
如果你的代码没有线程化,你应该使用StringBuilder而不是使用字符串连接,如果是的话,你应该使用StringBuffer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| //Note: if you have access to Java5+,
//use StringBuilder in preference to StringBuffer.
//All that has to be replaced is the class name.
//StringBuffer will work in Java 1.4, though.
appendWithDelimiter( StringBuffer buffer, String addition,
String delimiter ) {
if ( buffer.length() == 0) {
buffer.append(addition);
} else {
buffer.append(delimiter);
buffer.append(addition);
}
}
StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) {
appendWithDelimiter(parameterBuffer,"elementName","," );
}
if ( anotherCondition ) {
appendWithDelimiter(parameterBuffer,"anotherElementName","," );
}
//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); |
所以你可能会做一些事情来感觉你似乎正在寻找:
1)扩展List类 - 并向其添加join方法。 join方法只是简单地连接和添加分隔符(可以是join方法的参数)的工作
2)看起来Java 7将向java添加扩展方法 - 这允许您只是将特定方法附加到类上:因此您可以编写该连接方法并将其作为扩展方法添加到List甚至是采集。
解决方案1可能是唯一现实的,现在,虽然Java 7还没有出来:)但它应该工作得很好。
要同时使用这两个,您只需像往常一样将所有项目添加到列表或集合中,然后调用新的自定义方法以"加入"它们。
使用Dollar很简单,因为输入:
1
| String joined = $(aCollection).join(","); |
注意:它也适用于Array和其他数据类型
履行
在内部,它使用了一个非常巧妙的技巧:
1 2 3 4 5 6 7 8 9 10 11
| @Override
public String join(String separator) {
Separator sep = new Separator(separator);
StringBuilder sb = new StringBuilder();
for (T item : iterable) {
sb.append(sep).append(item);
}
return sb.toString();
} |
类Separator仅在第一次调用时返回空String,然后返回分隔符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Separator {
private final String separator;
private boolean wasCalled;
public Separator(String separator) {
this.separator = separator;
this.wasCalled = false;
}
@Override
public String toString() {
if (!wasCalled) {
wasCalled = true;
return"";
} else {
return separator;
}
}
} |
来自izb的版本略有改进[速度]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static String join(String[] strings, char del)
{
StringBuilder sb = new StringBuilder();
int len = strings.length;
if(len > 1)
{
len -= 1;
}else
{
return strings[0];
}
for (int i = 0; i < len; i++)
{
sb.append(strings[i]).append(del);
}
sb.append(strings[i]);
return sb.toString();
} |
修复回答Rob Dickerson。
它更容易使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static String join(String delimiter, String... values)
{
StringBuilder stringBuilder = new StringBuilder();
for (String value : values)
{
stringBuilder.append(value);
stringBuilder.append(delimiter);
}
String result = stringBuilder.toString();
return result.isEmpty() ? result : result.substring(0, result.length() - 1);
} |
我个人经常使用以下简单的解决方案进行日志记录:
1 2
| List lst = Arrays.asList("ab","bc","cd");
String str = lst.toString().replaceAll("[\\[\\]]",""); |
你让它变得有点复杂。让我们从你的例子的结尾开始:
1 2 3
| String parameterString ="";
if ( condition ) parameterString = appendWithDelimiter( parameterString,"elementName","," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString,"anotherElementName","," ); |
使用StringBuilder而不是String的变化很小,这变为:
1 2 3 4
| StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
... |
完成后(我假设您还必须检查其他一些条件),只需确保使用如下命令删除尾部逗号:
1 2
| if (parameterString.length() > 0)
parameterString.deleteCharAt(parameterString.length() - 1); |
最后,获取您想要的字符串
1
| parameterString.toString(); |
您还可以在第二次调用中替换","以附加可以设置为任何内容的通用分隔符字符串。如果你有一个你知道需要附加的东西的列表(非条件),你可以把这个代码放在一个带有字符串列表的方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static String join(String[] strings, char del)
{
StringBuffer sb = new StringBuffer();
int len = strings.length;
boolean appended = false;
for (int i = 0; i < len; i++)
{
if (appended)
{
sb.append(del);
}
sb.append(""+strings[i]);
appended = true;
}
return sb.toString();
} |
|