用Java构建带分隔符的字符串的最佳方法是什么?
在Java应用程序中工作时,最近我需要组合一个用逗号分隔的值列表,以将其传递给另一个Web服务,而无需事先知道会有多少个元素。我能想到的最好的办法是这样的:
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中,我可以改为执行以下操作,这感觉要优雅得多:
parameterArray = []; parameterArray << "elementName" if condition; parameterArray << "anotherElementName" if anotherCondition; parameterString = parameterArray.join(",");
但是由于Java缺少join命令,所以我找不到任何等效的东西。
那么,用Java做到这一点的最佳方法是什么?
解决方案
回答
使用基于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.
回答
我们可以为此使用Java的StringBuilder类型。还有" StringBuffer",但是它包含了额外的线程安全逻辑,这些逻辑通常是不必要的。
回答
为什么不编写自己的join()方法?它会将字符串和定界符字符串作为参数集合。在方法中,遍历集合,并在StringBuffer中建立结果。
回答
我们可以将其概括化,但是正如我们所说的,Java中没有联接。
这可能会更好。
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(); }
回答
我们可以尝试如下操作:
StringBuilder sb = new StringBuilder(); if (condition) { sb.append("elementName").append(","); } if (anotherCondition) { sb.append("anotherElementName").append(","); } String parameterString = sb.toString();
回答
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(); }
回答
我们可能应该将StringBuilder
与append
方法一起使用来构造结果,但是否则,这与Java必须提供的解决方案一样好。
回答
为什么在Java中不像在ruby中那样做相同的事情,那就是仅在将所有片段添加到数组之后才创建定界符分隔的字符串?
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 ...
编辑:固定追加的顺序。
回答
Java 8之前的版本:
Apache的commons lang是朋友,它提供了一种与我们在Ruby中引用的方法非常相似的join方法:
StringUtils.join(java.lang.Iterable,char)
Java 8:
Java 8通过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分隔符,CharSequence ...元素))
String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"
`String.join(CharSequence分隔符,Iterable <?扩展CharSequence>元素)
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, ",");
回答
所以基本上是这样的:
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(); } }
回答
Apache Commons StringUtils类具有一个join方法。
回答
不知道这是否真的更好,但是至少它使用的是StringBuilder,效率可能会更高。
如果可以在进行任何参数定界之前先建立参数列表,则下面是一种更通用的方法。
// 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应该是最快的方法:
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()。
回答
我会使用Google收藏夹。有一个不错的加入工具。
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); } }
我认为它与对象集合一起使用会更好,因为现在我们不必在加入对象之前将对象转换为字符串。
回答
如果代码未线程化,则应使用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);
最后,获得我们想要的字符串
parameterString.toString();
我们也可以在第二个调用中替换",",以添加可以设置为任何内容的通用分隔符字符串。如果我们知道需要添加的事物列表(无条件地),则可以将此代码放在采用字符串列表的方法中。
回答
//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();
回答
使用Java 5变量args,因此我们不必将所有字符串显式填充到集合或者数组中:
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")); } }
回答
因此,我们可以做一些事情来使自己感觉好像在寻找什么:
1)扩展List类并向其中添加join方法。 join方法将简单地进行串联和添加定界符的工作(这可能是join方法的一个参数)
2)看来Java 7将向Java添加扩展方法,从而允许我们仅将特定方法添加到类上:因此我们可以编写该join方法并将其作为扩展方法添加到List或者Collection中。
尽管Java 7尚未发布,但解决方案1可能是目前唯一可行的解决方案:)但它应该可以正常工作。
要同时使用这两种方法,只需像往常一样将所有项目添加到"列表"或者"集合"中,然后调用新的自定义方法来"加入"它们。
回答
使用StringBuilder和类Separator
StringBuilder buf = new StringBuilder(); Separator sep = new Separator(", "); for (String each : list) { buf.append(sep).append(each); }
分隔符包装定界符。分隔符由Separator的toString
方法返回,除非在第一次调用时返回空字符串!
类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; } }
回答
使用Dollar就像输入一样简单:
String joined = $(aCollection).join(",");
注意:它也适用于数组和其他数据类型
执行
在内部,它使用了一个非常巧妙的技巧:
@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; } } }
回答
izb版本的轻微改进[速度]:
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(); }