views:

135

answers:

7

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.

+1  A: 

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.

hb2pencil
Doing lots of String concatenation is evil in Java, and that conditional inside the loop will mean lots of unnecessary checks. These effects combined will yield poor performance for large lists of people.
Tom
You are right; however, the context seems to favor simplicity over performance. But I agree: this is not the appropriate solution for any production application. For the purposes of learning, though, I think it is a start. :)
hb2pencil
+1  A: 

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.

BalusC
A: 

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.

Tom
Please could whoever down-voted this leave a comment so that I can understand their objection to this solution?
Tom
A: 

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 + "', '")
}
AndHeCodedIt
A: 

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.

Edwin Buck
A: 

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 StringBuidler(list.get(0).toListEntry());

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

String stringList = buf.toString();
Samit G.
+1  A: 

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.

morisil