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

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

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通过StringJoinerString.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();
}

推荐阅读

    linux服务器基本命令?

    linux服务器基本命令?,地址,系统,设备,网络,工作,标准,信息,电脑,命令,密码,l

    linux服务器下载命令?

    linux服务器下载命令?,服务,密码,系统,档案,工具,网络,公共,百度,地址,认证,l

    linux服务端退出命令?

    linux服务端退出命令?,档案,命令,环境,异常,标准,网络,模式,终端,编辑,文件,l

    linux中启动服务命令?

    linux中启动服务命令?,服务,系统,命令,信息,工作,设备,网络,标准,名称,密码,l

    linux服务器常用命令?

    linux服务器常用命令?,工作,系统,地址,信息,命令,目录,管理,标准,设备,功能,

    linux使用命令的方法?

    linux使用命令的方法?,系统,信息,工具,标准,数据,命令,左下角,目录,文件夹,

    linux筛选服务命令?

    linux筛选服务命令?,服务,系统,状态,软件,环境,主体,技术,号码,发行,名称,查

    linux服务器保存命令?

    linux服务器保存命令?,时间,状态,档案,电脑,命令,信息,位置,编辑,文件,模式,L

    linux服务器扫盘命令?

    linux服务器扫盘命令?,地址,工作,命令,目录,数据,单位,名称,系统,管理,信息,L

    linux命令切换服务器?

    linux命令切换服务器?,地址,名称,系统,环境,实时,命令,服务器,脚本,路径,版

    linux磁盘列表命令?

    linux磁盘列表命令?,情况,管理,系统,单位,信息,数据,命令,磁盘,服务,时间,lin

    linux服务器搭建命令?

    linux服务器搭建命令?,系统,服务,软件,地址,平台,在线,密码,工具,环境,百度,l

    服务器重启命令linux?

    服务器重启命令linux?,工作,标准,设备,服务,系统,名称,命令,百度,网络,密码,

    linux服务端常用命令?

    linux服务端常用命令?,工作,地址,系统,网络,基础,命令,标准,工具,信息,管理,l

    linux禁用服务命令行?

    linux禁用服务命令行?,服务,系统,软件,管理,工具,信息,状态,平台,连续,技术,l

    linux停服务常用命令?

    linux停服务常用命令?,地址,工作,系统,命令,服务,信息,标准,管理,代码,进程,l

    linux服务器删除命令?

    linux服务器删除命令?,系统,服务,管理,情况,命令,工作,互动,地址,软件,较大,l

    linux开启服务命令?

    linux开启服务命令?,服务,标准,设备,工作,网络,系统,密码,命令,服务器,终端,

    linux服务器负荷命令?

    linux服务器负荷命令?,信息,电脑,中科,环境,工具,系统,平均,检测,情况,状态,l

    linux命令筛选列表?

    linux命令筛选列表?,工具,状态,位置,工作,预期,命令,名称,标准,数据,系统,在L