从java中的列表构建分隔字符串的最佳方法

Best way to build a delimited string from a list in java

我有一个对象列表,每个对象都有一个字符串属性。例如,我有一个List,每个Person都有一个firstName属性。我想构建一个逗号分隔的带引号的字符串,如下所示:

1
'James', 'Lily', 'Michael'

考虑到Java似乎没有一个连接方法(而且,这比一个简单的分隔字符串更复杂一些),最直接的方法是什么?我已经尝试了一段时间,但我的代码变得非常混乱,我可以使用一些新的输入。


我在同一个版本中使用了这个(ApacheStringUtils)

1
String joined ="'" + StringUtils.join(arrayOrList,"','") +"'";


JDK8 lambda的解决方案:

1
String quotedAndDelimited = Arrays.stream(values).collect(Collectors.joining("","",""","""));

它提供","作为分隔符,以及前缀和后缀。


用最直接的方法。让一个StringBuilder,在List上循环,用引号包围每个项目,将其附加到构建器中,如果还有更多内容,则附加逗号。

1
2
3
4
5
6
7
8
StringBuilder builder = new StringBuilder();
for (int i = 0; i < persons.size(); i++) {
    builder.append("'").append(persons.get(i)).append("'");
    if (i < persons.size() - 1) {
        builder.append(",");
    }
}
String string = builder.toString();

或者,您也可以使用Iterator

1
2
3
4
5
6
7
8
StringBuilder builder = new StringBuilder();
for (Iterator<Person> iter = persons.iterator(); iter.hasNext();) {
    builder.append("'").append(iter.next()).append("'");
    if (iter.hasNext()) {
        builder.append(",");
    }
}
String string = builder.toString();

注意,与使用+=字符串串联相比,使用StringBuilder更可取,因为后者在堆上隐式创建了一个新的字符串实例,当您有很多项时,这可能会占用性能和内存。


如果您想简单地手动执行,可以这样做:

1
2
3
4
5
6
7
String mystr ="";
for(int i = 0; i < personlist.size(); i++) {
  mystr +="\'" + personlist.get(i).firstName +"\'";
  if(i != (personlist.size() - 1)) {
    mystr +=",";
  }
}

现在mystr包含了你的列表。注意,只有当我们不处理列表中的最后一个元素(使用index personlist.size()-1)时才添加逗号。

当然,有更优雅/有效的方法来完成这一点,但在我看来,这一点是最清楚的。


下面是带期望的单元测试:

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
34
import static java.util.Arrays.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;  
import org.junit.Test;


public class JoinerTest {

    private static final Converter<Person, String> PERSON_CONVERTER =
        new Converter<Person, String>() {
            @Override
            public String convert(Person object) {
                return object.getFirstName();
            }
    };

    @Test
    public void shouldPresentFirstNames() {
        // given
        Person person1 = new Person();
        person1.setFirstName("foo");
        Person person2 = new Person();
        person2.setFirstName("bar");
        Joiner<Person> joiner = new Joiner<Person>(",","\'");

        // when
        String firstNames = joiner.join(PERSON_CONVERTER,
                        asList(person1, person2));

        // then
        assertThat(firstNames, is("\'foo\', \'bar\'"));
    }

}

转换器只是一个接口:

1
2
3
public interface Converter<F, T> {
    T convert(F object);
}

最后是细木工:

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
public class Joiner<T> {

    private final String delimiter;

    private final String quote;

    public Joiner(String delimiter, String quote) {
        this.delimiter = delimiter;
        this.quote = quote;
    }

    public String join(Converter<T, String> converter, Iterable<T> objects) {
        StringBuilder builder = new StringBuilder();
        for (T object : objects) {
            String string = converter.convert(object);
            builder.append(quote);
            builder.append(string);
            builder.append(quote);
            builder.append(delimiter);
        }
        if (builder.length() > 0) {
            builder.setLength(builder.length() - delimiter.length());
        }
        return builder.toString();
    }
}

通过使用不同的转换器,可以很容易地连接不同类型的属性。


我喜欢

1
2
3
4
5
6
7
8
9
10
if (list != null) {
  Iterator iter = list.iter();
  if (iter.hasNext()) {
    buffer.append(iter.next());
    while (iter.hasNext()) {
      buffer.append(",");
      buffer.append(iter.next());
    }
  }
}

它的主要优点是90%的执行时间可能在内部while循环中完成,并且这个解决方案只需要一个比较,就可以确定是否需要退出内部while循环。

其他解决方案也可以工作,但其解决方案如下:

1
2
3
4
5
6
while (iter.hasNext()) {
  buffer.append(iter.next());
  if (iter.hasNext()) {
    buffer.append(",");
  }
}

在while循环中嵌入不必要的if语句。这意味着当最后一个元素是唯一不同的元素时,必须对每个元素进行额外的比较。

通过将用于检查元素是否是最后一个元素的比较移动到一个比较(如果有多个元素),我们只需要为第一个元素以外的每个元素移动附加的","到一个预置的","


我建议您首先在Person类中添加一个方法:

1
2
3
4
5
6
Person {
    //...
    String toListEntry() {
        return"\'" + firstName +"\'";
    }
}

现在您可以使用:

1
2
3
4
5
6
7
List<Person> list = //... get the list
StringBuilder buf = new StringBuilder(list.get(0).toListEntry());

for(int i = 1; i < list.size(); i++)
    buf.append(" ," + list.get(i).toListEntry());

String stringList = buf.toString();

可以循环遍历列表中的每个项,并使用StringBuilder.append()方法将每个名称添加到列表中。这将使用String类,该类每次都会创建一个新的字符串实例,因为字符串是不可变的。

1)初始化字符串生成器2)使用list.size()获取列表中的项目数

1
int listSize = personList.size()

3)在字符串中添加第一个开头paren和引号。

1
StringBuilderinstance.append("('")

4)在调用append方法之前,使用for循环并将每个名称、结束引号、逗号和开始引号添加到StringBuilder实例中,检查我是否等于listsize如果您在最后一个索引中,请仅追加firstname、quote和结束paren:

1
2
3
4
5
6
7
for(int i = 0, i<listSize, i++)
{    
    if(i == listSize - 1)
        StringBuilderinstance.append(firstName +"')")
    else    
        StringBuilderinstance.append(firstName +"', '")
}

在核心Java中实现的一个简单的解决方案(没有外部依赖性)不在循环内进行条件评估,看起来可能是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static final char QUOTE = '\'';
private static final String SEPARATOR =",";

public static String join(List<Person> people) {
    if (people.isEmpty()) {
        return"";
    }
    final StringBuilder builder = new StringBuilder();
    for (Person person : people) {
        builder.append(QUOTE);
        builder.append(person.getFirstName());
        builder.append(QUOTE);
        builder.append(SEPARATOR);
    }
    builder.setLength(builder.length() - SEPARATOR.length());
    return builder.toString();
}

使用String缓冲区避免了大量的字符串串接,在Java中性能不好。