简体   繁体   中英

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

I have a list of objects, and each object has a string property. For example, I have a List<Person> and each Person has a firstName property. I want to build a comma-delimited, quoted string that looks like this:

'James', 'Lily', 'Michael'

Considering that java doesn't seem to have a join method (and besides, this is a bit more complicated than a simple delimited string), what's the most straightforward way to do this? I've been trying to code this for a bit but my code's gotten very messy and I could use some fresh input.

我用它来解决同样的问题( Apache StringUtils

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

If you want to do it simply and manually, you could do something like this:

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

Now mystr contains your list. Note that the comma is only added if we are not acting on the last element in the list (with index personlist.size() - 1).

Of course, there are more elegant/efficient methods to accomplish this, but this one is, in my opinion, the clearest.

Just use the most straightforward way. Have a StringBuilder , loop over the List , surround each item with quotes, append it to builder and if there's more to come, append the comma.

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

Alternatively, you can also use the Iterator :

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

Note that using a StringBuilder is preferred over using += string concatenation since the latter implicitly creates a new string instance on the heap which might be performance and memory hogging when you've a lot of items.

Jdk8 Lambda's solution:

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

This provides "," as the delimited, and a prefix and suffix of ".

I like

if (list != null) {
  Iterator iter = list.iter();
  if (iter.hasNext()) {
    buffer.append(iter.next());
    while (iter.hasNext()) {
      buffer.append(", ");
      buffer.append(iter.next());
    }
  }
}

The key advantage of this is that 90% of your execution time will probably be done in the inner while loop, and this solution only requires one comparison to determine if we need to exit the inner while loop.

Other solutions work too, but a solution that looks like:

while (iter.hasNext()) {
  buffer.append(iter.next());
  if (iter.hasNext()) {
    buffer.append(", ");
  }
}

embeds an unnecessary if statement within the while loop. This means an extra comparison will have to be made for each element, when the only differing element would be the last one.

By shifting the comparison for checking if the element is the last one to a comparison if there is more than one element, we only need to move the appended ", " to a prepended ", " for each element other than the first

Here is unit test with expectations:

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\'"));
    }

}

Converter is just an interface:

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

And finally Joiner:

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

By using different converters it is easy to join properties of different types.

A simple solution implemented in core Java (no external dependencies) that doesn't make a conditional evaluation inside the loop might look like this:

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

Using StringBuffer avoids lots of String concatenation, which is ill-performant in Java.

You can loop through each item in your List and use the StringBuilder.append() method to add each Name to the list. This aviods using the string class which will create a new instance of the string each time as string is immutable.

1) initialize your string builder 2) get the number of items in the list by using List.size()

int listSize = personList.size()

3) add the first opening paren and quote to your string

StringBuilderinstance.append("('")

4) use a for loop and add each name, ending quote, comma and opening quote to the stringBuilder instance, before calling the append method check to see if i equals listSize if you are at the last index only append the firstname, quote and closing paren:

for(int i = 0, i<listSize, i++)
{    
    if(i == listSize - 1)
        StringBuilderinstance.append(firstName + "')")
    else    
        StringBuilderinstance.append(firstName + "', '")
}

I would suggest you first add a method to your Person class:

Person {
    //...
    String toListEntry() {
        return "\'" + firstName + "\'";
    }
}

Now you can use this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM