用Java构建带分隔符的字符串的最佳方法是什么?


317

在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做到这一点的最佳方法是什么?


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

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

Answers:


542

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

2
我想知道-是否考虑到集合中对象的字符串表示形式是否包含分隔符?
GreenieMeanie

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

108
在Android上,您也可以使用TextUtils.join()。
詹姆斯·沃尔德

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

52

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

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

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

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

您不能List<String>将Initializer 更改为Iterator<?>并产生相同的效果吗?
k_g 2015年

@Tim Eg apache不会跳过空白字符串。
banterCZ 2015年


31

谷歌的番石榴库具有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字符串实用程序文章


26

在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示例的答案


20

您可以将其概括化,但是正如您所说的,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();
}

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

更好的是,使用Iterable<String>并使用可以迭代的事实。在您的示例中,您不需要集合中的项目数,因此这更为通用。
杰森·科恩

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

2
您想使用一个StringBuilder代替。它们相同,除了StringBuffer提供不必要的线程安全性。有人可以编辑它!
Casebash

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

17

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

15

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

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

为什么要StringBuilder代替StringBuffer?从StringBuilderjavadoc:

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


4
是的 另外,StringBuffer是线程安全的,而StringBuilder不是。
乔恩·昂斯托特

10

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

我认为它与对象集合一起使用会更好,因为现在您不必在加入对象之前将对象转换为字符串。





3

还有最少的一个(如果您不想仅仅为了加入字符串而将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();
}

使用delim代替File.separator。
Pradeep Kumar

3

使用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使其使用a StringBuilder而不是串联Strings呢?
Mohamed Nuur

@Mohamed Separator仅返回定界符,它不连接字符串本身。
akuhn 2011年

这是什么课Separator?为什么不只使用简单的String ..?!
maxxyme '16

2

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


2

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


2

如果您使用的是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(like 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集合的提交者。


2

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

1

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


1

为什么在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+)?您的附件也有错误的显示方式。
汤姆·霍顿

1

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

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

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

  • collectionToCommaDelimitedString(Collection coll)
  • collectionToDelimitedString(Collection coll,String delim)
  • arrayToDelimitedString(Object [] arr,字符串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);

0

您可以尝试如下操作:

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

看起来它会在字符串的末尾留下一个逗号,我希望避免。(当然,因为您知道它在那儿,所以可以对其进行修剪,但是它的味道也有点不雅致。)
肖恩·麦克马纳斯

0

所以基本上是这样的:

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

0

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

0

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


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

另外...语句“ +有一个很大的缺点,那就是为每个单个操作都创建了一个新的String实例”。编译器将从中生成StringBuilder.append()调用。
弗雷德里克

0

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


0

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

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

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


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

0

因此,您可以做一些事情来使自己感觉好像在寻找什么:

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

2)看来Java 7将向Java添加扩展方法-这允许您仅将特定的方法附加到类上:因此您可以编写该join方法并将其作为扩展方法添加到List或什至采集。

尽管Java 7尚未发布,但解决方案1可能是目前唯一可行的解​​决方案:)但它应该可以正常工作。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.