Самый простой способ разделить список запятыми?


104

Каков самый простой способ разделить список через запятую в Java?

Я знаю несколько способов сделать это, но мне интересно, какой из них лучший (где «лучший» означает самый ясный и / или самый короткий, а не самый эффективный.

У меня есть список, и я хочу перебрать его, печатая каждое значение. Я хочу вывести запятую между каждым элементом, но не после последнего (и не перед первым).

List --> Item ( , Item ) *
List --> ( Item , ) * Item

Пример раствора 1:

boolean isFirst = true;
for (Item i : list) {
  if (isFirst) {
    System.out.print(i);        // no comma
    isFirst = false;
  } else {
    System.out.print(", "+i);   // comma
  }
}

Пример решения 2 - создать подсписок:

if (list.size()>0) {
  System.out.print(list.get(0));   // no comma
  List theRest = list.subList(1, list.size());
  for (Item i : theRest) {
    System.out.print(", "+i);   // comma
  }
}

Пример раствора 3:

  Iterator<Item> i = list.iterator();
  if (i.hasNext()) {
    System.out.print(i.next());
    while (i.hasNext())
      System.out.print(", "+i.next());
  }

Они специально относятся к первому элементу; вместо этого можно было относиться к последнему специально.

Кстати, вот как List toString это реализовано (унаследовано от AbstractCollection) в Java 1.6:

public String toString() {
    Iterator<E> i = iterator();
    if (! i.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = i.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! i.hasNext())
            return sb.append(']').toString();
        sb.append(", ");
    }
}

Он выходит из цикла раньше, чтобы не ставить запятую после последнего элемента. Кстати: это первый раз, когда я вспоминаю, что видел "(этот сборник)"; вот код, чтобы спровоцировать это:

List l = new LinkedList();
l.add(l);
System.out.println(l);

Я приветствую любое решение, даже если оно использует неожиданные библиотеки (regexp?); а также решения на языках, отличных от Java (например, я думаю, что в Python / Ruby есть функция перемежения - как это реализовано?).

Уточнение : под библиотеками я имею в виду стандартные библиотеки Java. Что касается других библиотек, я рассматриваю их с другими языками и мне интересно узнать, как они реализованы.

В наборе инструментов EDIT упоминался аналогичный вопрос: последняя итерация расширенного цикла for в java

И еще: заслуживает ли последний элемент цикла отдельного рассмотрения?


Ответы:


226

Java 8 и новее

Использование StringJoinerкласса:

StringJoiner joiner = new StringJoiner(",");
for (Item item : list) {
    joiner.add(item.toString());
}
return joiner.toString();

Использование Stream, и Collectors:

return list.stream().
       map(Object::toString).
       collect(Collectors.joining(",")).toString();

Java 7 и ранее

См. Также # 285523

String delim = "";
for (Item i : list) {
    sb.append(delim).append(i);
    delim = ",";
}

1
Очень удивительный способ хранить состояние! Это почти объектно-ориентированный полиморфизм. Мне не нравится ненужное присваивание в каждом цикле, но я уверен, что это более эффективно, чем if. Большинство решений повторяют тест, который, как мы знаем, не будет правдой - хотя они и неэффективны, они, вероятно, самые четкие. Спасибо за ссылку!
13рен,

1
Мне это нравится, но из-за того, что это умно и удивительно, я подумал, что использовать его нецелесообразно (удивлять - нехорошо!). Однако я ничего не мог с собой поделать. Я все еще не уверен в этом; однако он настолько короткий, что его легко понять, если есть какой-нибудь комментарий.
13рен,

6
@ 13ren: Вам с дядей Бобом нужно поговорить о коде, который "умный и удивительный".
Стефан Кендалл

Я чувствую, что зря потратил годы, не зная об этом ^^;
Lake

Зачем Java превращать такую ​​задачу в уродливый код?
Эдуардо

82
org.apache.commons.lang3.StringUtils.join(list,",");

1
Нашел источник: svn.apache.org/viewvc/commons/proper/lang/trunk/src/java/org/… Метод соединения имеет 9 перегрузок. Итерационные решения похожи на «Пример решения 3»; основанные на индексе используют: if (i> startIndex) {<добавить разделитель>}
13ren

Хотя я действительно после реализации. Это хорошая идея, чтобы обернуть это функцией, но на практике мой разделитель иногда представляет собой новую строку, и каждая строка также имеет отступ до определенной глубины. Если ... join (list, "\ n" + indent) ... это всегда будет работать? Извините, просто подумал вслух.
13рен,

2
На мой взгляд, это самое красивое и кратчайшее решение
Джеспер Рённ-Йенсен

наверное, list.iterator ()?
Вануан

32

В Java 8 есть несколько новых способов сделать это:

  • В joinСпособ по String.
  • joiningКоллектор для потоков из Collectorsкласса.
  • StringJoinerКласс.

Пример:

// Util method for strings and other char sequences
List<String> strs = Arrays.asList("1", "2", "3");
String listStr1 = String.join(",", strs);

// For any type using streams and collectors
List<Object> objs = Arrays.asList(1, 2, 3);
String listStr2 = objs.stream()
    .map(Object::toString)
    .collect(joining(",", "[", "]"));

// Using the new StringJoiner class
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.setEmptyValue("<empty>");
for (Integer i : objs) {
  joiner.add(i.toString());
}
String listStr3 = joiner.toString();

Предполагает подход с использованием потоков import static java.util.stream.Collectors.joining;.


Это ИМХО, если вы используете java 8, лучший ответ.
scravy


7

Если вы используете Spring Framework, вы можете сделать это с помощью StringUtils :

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

Спасибо, а как это реализовано?
13рен,

6

На основе реализации Java List toString:

Iterator i = list.iterator();
for (;;) {
  sb.append(i.next());
  if (! i.hasNext()) break;
  ab.append(", ");
}

Он использует такую ​​грамматику:

List --> (Item , )* Item

Будучи основанным на последнем, а не на первом, он может проверять пропущенную запятую с помощью того же теста, чтобы проверить наличие конца списка. Я думаю, что это очень элегантно, но я не уверен в ясности.




3
for(int i=0, length=list.size(); i<length; i++)
  result+=(i==0?"":", ") + list.get(i);

Да, это немного загадочно, но это альтернатива тому, что уже было опубликовано.
cherouvim

1
Я думаю, что код выглядит нормально, ИМХО он красивее, чем ваш другой ответ, и его вряд ли «загадочно». Оберните это методом под названием Join и смехом, но все же я ожидал бы, что у языка будет лучший способ решения действительно распространенной проблемы.
tarn

@tarn: ты думаешь, что это выглядит хорошо, потому что у тебя есть фон Python / perl;) Мне это тоже нравится
cherouvim

3

Один из вариантов цикла foreach:

StringBuilder sb = new StringBuilder();
for(String s:list){
  if (sb.length()>0) sb.append(",");
  sb.append(s);
}

2
StringBuffer result = new StringBuffer();
for(Iterator it=list.iterator; it.hasNext(); ) {
  if (result.length()>0)
    result.append(", ");
  result.append(it.next());
}

Обновление : как Дэйв Уэбб упомянул в комментариях, это может не дать правильных результатов, если первые элементы в списке являются пустыми строками.


Мех .. все еще есть если в цикле.
sfossen

Вы можете использовать цикл foreach, чтобы сделать его короче.
Питер Лоури,

Это не сработает, если первый элемент в списке - пустая строка.
Дэйв Уэбб,

@ Дэйв Уэбб: Да, спасибо. Хотя в вопросе такого требования нет.
cherouvim

@cherovium: вопрос не говорит о том, что ни один из элементов не будет пустой строкой, поэтому это неявное требование. Если вы создаете файл CSV, пустые поля могут быть довольно обычным явлением.
Дэйв Уэбб

2

Я обычно так делаю:

StringBuffer sb = new StringBuffer();
Iterator it = myList.iterator();
if (it.hasNext()) { sb.append(it.next().toString()); }
while (it.hasNext()) { sb.append(",").append(it.next().toString()); }

Хотя я думаю, что с этого момента я буду проходить эту проверку в соответствии с реализацией Java;)


Та же логика, что и в примере 3, мне он нравится, потому что в нем нет ничего лишнего. Однако я не знаю, насколько это ясно. Кстати: это немного эффективнее, если вы поместите while внутри if, избегая двух тестов, когда список пуст. Это также проясняет мне ситуацию.
13рен

Ради бога, используйте
StringBuilder

2

Если вы можете использовать Groovy (который работает на JVM):

def list = ['a', 'b', 'c', 'd']
println list.join(',')

спасибо, но меня интересуют реализации. Как это реализовано?
13рен,

2

(Скопируйте и вставьте мой собственный ответ отсюда .) Многие из описанных здесь решений немного чрезмерны, ИМХО, особенно те, которые полагаются на внешние библиотеки. Есть хорошая чистая, ясная идиома для создания списка, разделенного запятыми, который я всегда использовал. Он основан на условном операторе (?):

Изменить : исходное решение правильное, но неоптимальное согласно комментариям. Пробуем второй раз:

int[] array = {1, 2, 3};
StringBuilder builder = new StringBuilder();
for (int i = 0 ;  i < array.length; i++)
       builder.append(i == 0 ? "" : ",").append(array[i]);

Вот и все, 4 строки кода, включая объявление массива и StringBuilder.

2-е изменение : если вы имеете дело с итератором:

    List<Integer> list = Arrays.asList(1, 2, 3);
    StringBuilder builder = new StringBuilder();
    for (Iterator it = list.iterator(); it.hasNext();)
        builder.append(it.next()).append(it.hasNext() ? "," : "");

Это и stackoverflow.com/questions/668952/… выше аналогичны. Это коротко и понятно, но если у вас есть только итератор, вам сначала нужно преобразовать. Неэффективно, но, может быть, оно того стоит для ясности такого подхода?
13рен,

Ух, конкатенация строк создает временный StringBuilder при использовании StringBuilder. Неэффективно. Лучше (больше похоже на Java, но лучше работает код) было бы "if (i> 0) {builder.append (',');}", за которым следует builder.append (array [i]);
Эдди,

Да, если вы посмотрите в байт-код, вы правы. Не могу поверить, что такой простой вопрос может стать таким сложным. Интересно, может ли компилятор здесь оптимизироваться.
Жюльен Частанг,

Обеспечено второе, надеюсь, лучшее решение.
Julien Chastang,

Может быть, лучше их разделить (чтобы люди могли проголосовать за одно или другое).
13рен,

1

Я обычно использую что-то похожее на версию 3. Хорошо работает c / c ++ / bash / ...: P


1

Я не компилировал его ... но должен работать (или быть близок к работе).

public static <T> String toString(final List<T> list, 
                                  final String delim)
{
    final StringBuilder builder;

    builder = new StringBuilder();

    for(final T item : list)
    {
        builder.append(item);
        builder.append(delim);
    }

    // kill the last delim
    builder.setLength(builder.length() - delim.length());

    return (builder.toString());
}

Мне нравится этот способ, я бы предпочел другое форматирование. Но упомяну одно небольшое исправление: если список пуст, это вызовет исключение IndexOutOfBounds. Итак, либо сделайте проверку до setLength, либо используйте Math.max (0, ...)
tb-

1

Это очень коротко, очень ясно, но придает моей чувствительности ползучий ужас. Также немного неудобно адаптироваться к разным разделителям, особенно если это String (не char).

for (Item i : list)
  sb.append(',').append(i);
if (sb.charAt(0)==',') sb.deleteCharAt(0);

Вдохновлено: последней версией расширенного цикла for в java.


1
StringBuffer sb = new StringBuffer();

for (int i = 0; i < myList.size(); i++)
{ 
    if (i > 0) 
    {
        sb.append(", ");
    }

    sb.append(myList.get(i)); 
}

Я считаю, что это очень легко встроить в статический служебный класс, а также его очень легко читать и понимать. Также не требуется никаких дополнительных переменных или состояния, кроме самого списка, цикла и разделителя.
Иоахим Х. Скей,

Это даст что-то вроде:Item1Item2, Item3, Item4,

Ради всего святого, используйте
StringBuilder

1

Мне немного нравится этот подход, который я нашел некоторое время назад в блоге. К сожалению, я не помню имя / URL блога.

Вы можете создать служебный / вспомогательный класс, который выглядит так:

private class Delimiter
{
    private final String delimiter;
    private boolean first = true;

    public Delimiter(String delimiter)
    {
        this.delimiter = delimiter;
    }

    @Override
    public String toString()
    {
        if (first) {
            first = false;
            return "";
        }

        return delimiter;
    }
}

Использовать вспомогательный класс просто:

StringBuilder sb = new StringBuilder();
Delimiter delimiter = new Delimiter(", ");

for (String item : list) {
    sb.append(delimiter);
    sb.append(item);
}

Замечание: это должен быть «разделитель», а не «разделитель».
Майкл Майерс

1

Поскольку ваш разделитель "," вы можете использовать любое из следующего:

public class StringDelim {

    public static void removeBrackets(String string) {
        System.out.println(string.substring(1, string.length() - 1));
    }

    public static void main(String... args) {
        // Array.toString() example
        String [] arr = {"Hi" , "My", "name", "is", "br3nt"};
        String string = Arrays.toString(arr);
        removeBrackets(string);

        // List#toString() example
        List<String> list = new ArrayList<String>();
        list.add("Hi");
        list.add("My");
        list.add("name");
        list.add("is");
        list.add("br3nt");
        string = list.toString();
        removeBrackets(string);

        // Map#values().toString() example
        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("1", "Hi");
        map.put("2", "My");
        map.put("3", "name");
        map.put("4", "is");
        map.put("5", "br3nt");
        System.out.println(map.values().toString());
        removeBrackets(string);

        // Enum#toString() example
        EnumSet<Days> set = EnumSet.allOf(Days.class);
        string = set.toString();
        removeBrackets(string);
    }

    public enum Days {
        MON("Monday"),
        TUE("Tuesday"),
        WED("Wednesday"),
        THU("Thursday"),
        FRI("Friday"),
        SAT("Saturday"),
        SUN("Sunday");

        private final String day;

        Days(String day) {this.day = day;}
        public String toString() {return this.day;}
    }
}

Если ваш разделитель - НИЧЕГО другое, то это не сработает для вас.


1

Мне нравится это решение:

String delim = " - ", string = "";

for (String item : myCollection)
    string += delim + item;

string = string.substring(delim.length());

Я предполагаю, что он также может использовать StringBuilder.


1

На мой взгляд, это самое простое для чтения и понимания:

StringBuilder sb = new StringBuilder();
for(String string : strings) {
    sb.append(string).append(',');
}
sb.setLength(sb.length() - 1);
String result = sb.toString();

0

В Python это просто

",". присоединиться (ваш список)

В C # есть статический метод класса String

String.Join (",", ваш список строк)

Извините, я не уверен насчет Java, но подумал, что отвечу, когда вы спросите о других языках. Я уверен, что в Java было бы что-то подобное.


IIRC, вы получаете трейлинг в версии .Net. :-(

1
Только что протестировано, без конечного разделителя в .NET или Python
tarn

1
Спасибо! Я нашел его источник: svn.python.org/view/python/branches/release22-branch/Objects/… выполните поиск по запросу «Catenate everything». Он пропускает разделитель для последнего элемента.
13рен,

@ 13ren Отлично сделано! Помогает? У меня была блокировка, и я быстро вспомнил, почему я решил не программировать на C в эти дни: P
tarn

@tarn, да, это интересный пример, потому что он добавляет команду, только если это не последний элемент: if (i <seqlen - 1) {<add separator>}
13ren

0

Вы также можете безоговорочно добавить строку разделителя, а после цикла удалить лишний разделитель в конце. Тогда "если список пуст, вернуть эту строку" в начале позволит вам избежать проверки в конце (так как вы не можете удалить символы из пустого списка)

Итак, действительно вопрос:

«Как вы думаете, с учетом цикла и« если »их можно соединить вместе?»


0
public static String join (List<String> list, String separator) {
  String listToString = "";

  if (list == null || list.isEmpty()) {
   return listToString;
  }

  for (String element : list) {
   listToString += element + separator;
  }

  listToString = listToString.substring(0, separator.length());

  return listToString;
}

1
Я думаю, вы хотели сказатьlistToString.substring(0, listToString.length()-separator.length());
13

0
if (array.length>0)          // edited in response Joachim's comment
  sb.append(array[i]);
for (int i=1; i<array.length; i++)
  sb.append(",").append(array[i]);

На основе самого четкого способа разделения списка через запятую (Java)?

Используя эту идею: заслуживает ли последний элемент в цикле отдельного рассмотрения?


1
Чтобы это сработало, вам необходимо знать, что в списке есть хотя бы 1 элемент, иначе ваш первый цикл for выйдет из строя с исключением IndexOutOfBoundsException.
Иоахим Х. Скей,

@Joachim спасибо, ты конечно прав. Какое уродливое решение я написал! Я перейду for (int i=0; i<1; i++)на if (array.length>0).
13рен,

0
public String toString(List<Item> items)
{
    StringBuilder sb = new StringBuilder("[");

    for (Item item : items)
    {
        sb.append(item).append(", ");
    }

    if (sb.length() >= 2)
    {
        //looks cleaner in C# sb.Length -= 2;
        sb.setLength(sb.length() - 2);
    }

    sb.append("]");

    return sb.toString();
}

1
Только что заметил, что это похоже на ответ, который дал
TofuBeer

0

Ни один из ответов пока не использует рекурсию ...

public class Main {

    public static String toString(List<String> list, char d) {
        int n = list.size();
        if(n==0) return "";
        return n > 1 ? Main.toString(list.subList(0, n - 1), d) + d
                  + list.get(n - 1) : list.get(0);
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"1","2","3"});
        System.out.println(Main.toString(list, ','));
    }

}

0
private String betweenComma(ArrayList<String> strings) {
    String united = "";
    for (String string : strings) {
        united = united + "," + string;
    }
    return united.replaceFirst(",", "");
}

-1

Метод

String join(List<Object> collection, String delimiter){
    StringBuilder stringBuilder = new StringBuilder();
    int size = collection.size();
    for (Object value : collection) {
        size --;
        if(size > 0){
            stringBuilder.append(value).append(delimiter);
        }
    }

    return stringBuilder.toString();
}

использование

Учитывая массив [1,2,3]

join(myArray, ",") // 1,2,3
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.