在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缺少连接命令,所以我找不到任何等效的命令。

那么,用Java做到这一点的最好方法是什么?

评论

StringbBilder是必经之路-java.lang.StringBuilder。

对于Java 8,请查看以下答案:stackoverflow.com/a/22577623/1115554

#1 楼

Java 8之前的版本:

Apache的commons lang是您的朋友-它提供了一种与您在Ruby中所引用的方法非常相似的join方法:

StringUtils.join(java.lang.Iterable,char)


Java 8:

Java 8通过StringJoinerString.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"


评论


我想知道-是否考虑集合中对象的字符串表示形式是否包含定界符本身?

– GreenieMeanie
09年7月8日在20:03

正是我在寻找什么:来自org.apache.commons.lang3.StringUtils包的StringUtils.join(java.util.Collection,String),jar文件是commons-lang3-3.0.1.jar

– Umar
2011年8月30日6:58



在Android上,您也可以使用TextUtils.join()。

–詹姆斯·沃尔德(James Wald)
2012年2月29日在23:07

这不是最好的方法。即使Collection中的Object为null / empty,它也将始终添加char。它很干净,但有时会打印一个双定界符char。

–斯蒂芬·席尔克(Stephan Schielke)
2012年8月30日在8:32

我想知道如果这样的API无法处理空值和空值有什么意义?

– arunwithasmile
8月28日10:43



#2 楼

您可以编写一个适用于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, ",");


评论


如果已经存在至少两个实现(apache和guava),为什么还要编写自己的方法?

– Timofey
2012年11月24日,11:16

例如,如果希望减少外部依赖关系,这可能很有用。

–托马斯·祖姆布伦
13年2月9日在21:10

我有点喜欢用loopDelim代替条件。这是一种hack,但是它从循环中删除了条件语句。我仍然更喜欢使用迭代器并在循环之前添加第一个元素,但这确实是一个不错的技巧。

–Vlasec
15年1月13日在19:13

您是否可以将intitializer中的List 更改为Iterator <?>并具有相同的效果?

– k_g
2015年2月22日,0:56

@Tim例如apache不会跳过空白字符串。

–banterCZ
2015年5月19日晚上8:55

#3 楼

在Android上,公用的StringUtils类不可用,因此我使用了

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


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

#4 楼

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"



这里是有关Guava字符串实用程序的文章。

#5 楼

在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示例。

#6 楼

您可以将其概括化,但是正如您所说的,Java中没有join。

这可能会更好。

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


评论


我同意这个答案,但是有人可以编辑签名,以便它接受Collection 而不是AbstractCollection 吗?其余代码应该相同,但是我认为AbstractCollection是实现细节,在这里无关紧要。

– Outlaw程序员
08-09-15 at 14:19

更好的是,使用Iterable 并仅使用可以对其进行迭代的事实。在您的示例中,您不需要集合中的项目数,因此这更为通用。

–Jason Cohen
08/09/15在18:34

甚至更好,使用Iterable <?扩展Charsequence>,然后您就可以接受StringBuilders和String以及流和其他类似字符串的对象的集合。 :)

–Jason Cohen
08年9月15日在18:35

您想改用StringBuilder。它们相同,只是StringBuffer提供了不必要的线程安全性。有人可以编辑它!

– Casebash
2010年8月11日4:43



此代码有几个错误。 1. CharSequence具有大写的s。 2. s.iterator()返回Iterator <?扩展CharSequence>。 3. Iterable没有isEmpty()方法,请改用next()方法

– Casebash
2010年8月12日,0:42



#7 楼

在Java 8中,您可以这样操作:

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


如果列表包含空值,则可以使用:

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


它还支持前缀和后缀:

 list.stream().map(String::valueOf)
        .collect(Collectors.joining(delimiter, prefix, suffix));
 


#8 楼

使用基于java.lang.StringBuilder的方法! (“可变的字符序列。”)

就像您提到的那样,所有这些字符串连接都在各处创建Strings。 StringBuilder不会这样做。

为什么用StringBuilder代替StringBuffer?来自StringBuilder javadoc:


在可能的情况下,建议优先使用此类而不是StringBuffer,因为在大多数实现中它会更快。


评论


是的另外,StringBuffer是线程安全的,而StringBuilder不是。

–琼·昂斯托特(Jon Onstott)
13年3月21日在17:33

#9 楼

我会使用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);
    }
}


我认为它与对象集合一起使用效果更好,因为现在您不必先将对象转换为字符串你加入他们。

#10 楼

Apache commons StringUtils类具有一个join方法。

#11 楼

Java 8

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


#12 楼

您可以为此使用Java的StringBuilder类型。还有StringBuffer,但是它包含通常不需要的额外线程安全逻辑。

#13 楼

还有最少的一个(如果您不只是为了加入字符串而将Apache Commons或Guava包含在项目依赖项中)

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


评论


使用delim代替File.separator。

– Pradeep Kumar
2015年2月1日,下午1:53

#14 楼

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

}


评论


如何修改Separator,使其使用StringBuilder而不是连接字符串呢?

–穆罕默德·努尔(Mohamed Nuur)
2011年6月2日22:14

@Mohamed Separator只是返回分隔符,它不连接字符串本身。

–akuaku
2011年6月9日下午0:41

此类分隔符是什么???为什么不只使用简单的String ..?!

– maxxyme
16 Dec 28'在12:14

#15 楼

Java 8本机类型

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));


Java 8自定义对象:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));


#16 楼

为什么不编写自己的join()方法?它会将字符串和定界符字符串作为参数集合。在该方法内,对集合进行迭代,并在StringBuffer中建立结果。

#17 楼

如果您使用的是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

#18 楼

如果使用的是Eclipse Collections,则可以使用makeString()appendString()

makeString()返回String表示形式,类似于toString()

它具有三种形式


makeString(start, separator, end)

makeString(separator)默认开始和结束为空字符串

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。它具有相同的三种形式,并带有一个附加的第一个参数Appendable。

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


如果不能将集合转换为Eclipse Collections类型,则只需对其进行调整即可使用相关的适配器。

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


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

#19 楼

您可能应该使用带有StringBuilder方法的append来构造结果,但是否则,这与Java必须提供的解决方案一样好。

#20 楼

为什么在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...。

编辑:固定了追加的顺序。

评论


是的,为什么要使用StringBuffer而不是StringBuilder(因为您正在使用Java 1.5+)?您的附件也有错误的显示方式。

– Tom Hawtin-大头钉
08/09/15在14:22

#21 楼

使用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"));
    }
}


#22 楼

对于在Spring上下文中的人来说,他们的StringUtils类也很有用:

有许多有用的快捷方式,例如:


collectionToCommaDelimitedString(Collection coll)
collectionToDelimitedString(Collection coll,String delim)
arrayToDelimitedString(Object [] arr,String delim)

等等。

如果您还没有使用Java 8并且已经在Spring上下文中,那么这可能会有所帮助。

与Apache Commons相比,我更喜欢它(尽管也很好) ),以便像这样更轻松地获得收藏支持:

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


#23 楼

您可以尝试这样的操作:

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


评论


看起来它会在字符串的末尾留下一个逗号,我希望避免。 (当然,由于您知道它的存在,因此可以对其进行修剪,但这也有点不雅观。)

– Sean McMains
08/09/15在14:36

#24 楼

所以基本上是这样的:

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


评论


这里的问题是他似乎正在调用appendWithDelimiter()分配。解决方案应仅实例一个StringBuffer并使用该单个实例。

– Stu Thompson
08/09/15在14:22

#25 楼

不知道这是否真的更好,但是至少它使用的是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");
}


#26 楼

您的方法还不错,但是应该使用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()。

评论


在没有充分理由的情况下,在此处使用StringBuffer只会使其变慢。使用+运算符将在内部使用速度更快的StringBuilder,因此,除了使用StringBuffer不需要线程安全之外,他没有其他优势。

–弗雷德里克
09年7月8日在20:14

另外...语句“ +的最大缺点是,为每个单个操作都创建了一个新的String实例”。编译器将根据它们生成StringBuilder.append()调用。

–弗雷德里克
09年7月8日在20:17

#27 楼

如果您的代码未线程化,则应使用StringBuilder,如果不是,则应使用StringBuffer。而不是使用字符串连接。

#28 楼

您正在使它变得比必需的更加复杂。让我们从示例的结尾开始:

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


您也可以在第二个调用中替换“,”以追加具有可以设置为任何内容的通用定界符字符串。如果您知道需要附加的事物列表(无条件地),则可以将此代码放在采用字符串列表的方法中。

#29 楼

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


#30 楼

因此,您可能需要做几件事来感觉到您正在寻找的感觉:

1)扩展List类-并向其中添加join方法。 join方法将简单地进行串联和添加定界符的工作(这可能是join方法的一个参数)。

2)Java 7似乎将向Java添加扩展方法-它只允许您将特定的方法附加到类上:因此您可以编写该join方法并将其作为扩展方法添加到List或Collection中。

解决方案1可能是唯一的现实现在,一个,虽然因为Java 7还没有发布:)但它应该可以正常工作。

要使用这两个功能,只需将所有项目添加为List或Collection作为通常,然后调用新的自定义方法以“加入”它们。