在Java中构build一系列分隔项目的最佳方法是什么?

在Java应用程序中工作时,我最近需要组装一个以逗号分隔的值列表,以传递给另一个Web服务,而不知道预先存在多less个元素。 我能从头顶上拿出最好的东西就是这样的:

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

我意识到这并不是特别有效率,因为在各处都有string的创build,但是我要求的不仅仅是优化。

在Ruby中,我可以做这样的事情,而不是更优雅:

 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通过StringJoinerString.join()提供了开箱StringJoiner String.join() 。 下面的代码片段展示了如何使用它们:

StringJoiner

 StringJoiner joiner = new StringJoiner(","); joiner.add("01").add("02").add("03"); String joinedString = joiner.toString(); // "01,02,03" 

String.join(CharSequence delimiter, CharSequence... elements))

 String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06" 

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

 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的连接式实用程序方法

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

然后像这样使用它:

  List<String> list = new ArrayList<String>(); if( condition ) list.add("elementName"); if( anotherCondition ) list.add("anotherElementName"); join(list, ","); 

在Android的情况下,来自commons的StringUtils类是不可用的,所以我用这个

 android.text.TextUtils.join(CharSequence delimiter, Iterable tokens) 

http://developer.android.com/reference/android/text/TextUtils.html

Google的Guava库有com.google.common.base.Joiner类,可以帮助解决这些任务。

样品:

 "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" 

这里是关于番石榴string实用程序的文章 。

在Java 8中,您可以使用String.join()

 List<String> list = Arrays.asList("foo", "bar", "baz"); String joined = String.join(" and ", list); // "foo and bar and baz" 

也可以看看Stream API示例的这个答案 。

你可以概括它,但是没有joinJava,正如你所说的那样。

这可能会更好。

 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.lang.StringBuilder的方法! (“一个可变的字符序列”)

就像你提到的那样,所有这些string连接都在创buildStrings。 StringBuilder不会那样做。

为什么StringBuilder而不是StringBuffer ? 从StringBuilder javadoc:

在可能的情况下,build议将此类优先用于StringBuffer,因为在大多数实现中它将更快。

我会使用Google Collections。 有一个很好的join设施。
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

但如果我想自己写,

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

我认为它对于对象集合来说效果更好,因为现在在join对象之前不必将对象转换为string。

在Java 8中,你可以这样做:

 list.stream().map(Object::toString) .collect(Collectors.joining(delimiter)); 

如果列表中有空值,则可以使用:

 list.stream().map(String::valueOf) .collect(Collectors.joining(delimiter)) 

Apache commons StringUtils类有一个连接方法。

您可以使用Java的StringBuildertypes。 还有StringBuffer ,但它包含额外的线程安全逻辑,通常是不必要的。

Java 8

 stringCollection.stream().collect(Collectors.joining(", ")); 

如果你正在使用Spring MVC,那么你可以尝试下面的步骤。

 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

使用StringBuilder和类Separator

 StringBuilder buf = new StringBuilder(); Separator sep = new Separator(", "); for (String each : list) { buf.append(sep).append(each); } 

分隔符包裹分隔符。 分隔符的toString方法返回分隔符,除非第一次调用返回空string!

Separator源代码

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

为什么不写你自己的join()方法? 这将需要作为参数集合的string和分隔符string。 在该方法内迭代集合,并在StringBuffer中build立结果。

你可能应该使用一个StringBuilderappend方法来构造你的结果,否则这个解决scheme就像Java所能提供的一样。

你为什么不用Java做与你在做的事情相同的事情,那就是在你把所有的部分添加到数组之后才创build分隔符分隔的string?

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

你可能想要在一个单独的帮助器方法中移动该循环,并使用StringBuilder而不是StringBuffer …

编辑 :修正了追加的顺序。

使用Java 5variables参数,所以你不必将所有的string明确地写入一个集合或数组中:

 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支持来说更简单,就像这样:

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

最小的一个(如果你不想为了连接string而将Apache Commons或Gauva包含到项目依赖项中)

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

如果您正在使用Eclipse集合 ,则可以使用makeString()appendString()

makeString()返回一个String表示,类似于toString()

它有三种forms

  • makeString(start, separator, end)
  • makeString(separator)默认开始和结束为空string
  • makeString()默认分隔符为", " (逗号和空格)

代码示例:

 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 。 它有三个相同的forms,第一个参数是Appendable。

 MutableList<Integer> list = FastList.newListWith(1, 2, 3); Appendable appendable = new StringBuilder(); list.appendString(appendable, "[", "/", "]"); assertEquals("[1/2/3]", appendable.toString()); 

如果您不能将您的集合转换为Eclipse集合types,只需将其与相关的适配器相匹配即可。

 List<Object> list = ...; ListAdapter.adapt(list).makeString(","); 

注意:我是Eclipse集合的提交者。

你可以尝试这样的事情:

 StringBuilder sb = new StringBuilder(); if (condition) { sb.append("elementName").append(","); } if (anotherCondition) { sb.append("anotherElementName").append(","); } String parameterString = sb.toString(); 

所以基本上是这样的:

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

不知道这真的是更好,但至less它使用StringBuilder,可能会稍微更有效率。

如果您可以在进行任何参数分隔之前构build参数列表,则在下面是更通用的方法。

 // 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而不是使用+号。 +有一个很大的缺点,就是每个单独的操作都会创build一个新的String实例。 串越长,开销就越大。 所以使用StringBuffer应该是最快的方法:

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

完成创buildstring之后,只需在返回的StringBuffer上调用toString()即可。

如果你的代码不是线程的话,你应该使用StringBuilder而不是StringBuffer,如果是的话。

你正在使这一点比它应该更复杂一点。 让我们从你的例子的结尾开始:

 String parameterString = ""; if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," ); if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," ); 

随着使用StringBuilder而不是一个string的小改变,这成为:

 StringBuilder parameterString = new StringBuilder(); if (condition) parameterString.append("elementName").append(","); if (anotherCondition) parameterString.append("anotherElementName").append(","); ... 

当你完成了(我假设你还要检查其他一些条件),只要确保你用下面的命令删除了拖尾的逗号:

 if (parameterString.length() > 0) parameterString.deleteCharAt(parameterString.length() - 1); 

最后,得到你想要的string

 parameterString.toString(); 

您也可以在第二次调用中replace“,”来附加一个可以设置为任意值的通用分隔符string。 如果你有一个列表,你知道你需要追加(非条件),你可以把这个代码放在一个方法中,该方法需要一个string列表。

 //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)扩展列表类 – 并添加连接方法。 连接方法只是简单地进行连接和添加分隔符的工作(这可能是连接方法的参数)

2)它看起来像Java 7将扩展方法添加到Java – 它允许你只需要附加一个特定的方法到一个类:所以你可以编写该连接方法,并将其作为扩展方法添加到列表甚至到采集。

解决scheme1可能是现在唯一现实的,但是因为Java 7还没有出来:)但它应该工作得很好。

要使用这两种方法,只需像往常一样将所有项目添加到列表或集合中,然后调用新的自定义方法来“join”它们。

使用美元很简单,因为input:

 String joined = $(aCollection).join(","); 

NB:它也适用于数组和其他数据types

履行

在内部,它使用了一个非常整洁的技巧:

 @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,然后返回分隔符:

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