Найскладніший спосіб створення розділених комами рядків із колекції / масиву / списку?


98

Під час роботи з базами даних я помітив, що я пишу рядки запитів, і в цих рядках я повинен поставити декілька обмежень у пункті-де зі списку / масиву / колекції. Має виглядати так:

select * from customer 
where customer.id in (34, 26, ..., 2);

Ви можете спростити це, зменшивши це до питання про те, що у вас є колекція рядків і хочете створити розділений комами список цих рядків лише в одній рядку.

Мій підхід, який я застосовував до цього часу, є чимось таким:

String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
    if(first) {
        result+=string;
        first=false;
    } else {
        result+=","+string;
    }
}

Але це як ви бачите дуже некрасиво. Ви не можете побачити, що там відбувається з першого погляду, особливо коли сконструйовані рядки (як і кожен SQL-запит) ускладнюються.

Який ваш (більш) елегантний спосіб?


Імовірно, SQL, показаний вище, насправді повинен виглядати приблизно так: виберіть * від клієнта, де customer.id в (34, 26, 2);
Дональ

Існує складна частина, коли самі елементи (рядки) списку містять коми або подвійні лапки, і їх потрібно уникати цитатами. Якщо я нічого не пропустив, наведені вище приклади не враховують, і я ненавиджу ідею прокручувати всі тексти та шукати коми. Чи у вас є річ, що існує кращий спосіб вирішити це?
Самурайська дівчина

перевірити цю відповідь ... stackoverflow.com/a/15815631/728610
Арвінд Шрідхаран

Ви коли-небудь перевіряли stackoverflow.com/questions/10850753/… ?
Хірен Патель

Відповіді:


85

Примітка. Ці відповіді були хорошими, коли це було написано 11 років тому, але зараз є набагато кращі варіанти зробити це більш чисто в одному рядку, обидва з використанням лише вбудованих Java класів або використання бібліотеки утиліт. Дивіться інші відповіді нижче.


Оскільки рядки незмінні, можливо, ви захочете використовувати клас StringBuilder, якщо ви збираєтесь змінити String в коді.

Клас StringBuilder можна розглядати як змінний об'єкт String, який виділяє більше пам'яті при зміні його вмісту.

Оригінальну пропозицію у запитанні можна написати ще чіткіше та ефективніше, дбаючи про зайву кінцеву кому :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";

7
Зверніть увагу, що для цього у вашій колекції є принаймні один елемент.
Гюс

3
Дивіться головну відповідь - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel

Дивіться запропоновані виправлення порожнього списку.
gimel

1
відповідь гуави краще. не потрібно винаходити колесо.
davidjnelson

1
@ xtreme-biker З досить сучасним компілятором StringBuilder може використовуватися автоматично. Перевірте своє оточення, перш ніж використовувати + =. Див stackoverflow.com/questions/1532461 / ...
Гімел

89

Використовуйте Google гуави API «сек joinметод:

Joiner.on(",").join(collectionOfStrings);

4
У наш час клас називається Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google / ...
Jonik

2
І сьогодні колекції застаріли. Використовуйте Google Guava замість цього.
darioo

12
Тим часом org.apache.commons.lang.StringUtils залишається незмінним. :-)
Псалом Огре3333

1
гуава рушила. дивіться github.com/google/guava/wiki/StringsExplained
gimel

76

Я просто подивився на код, який це зробив сьогодні. Це варіант у відповіді AviewAnew.

collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");

Використовувані нами StringUtils (<- commons.lang 2.x або посилання commons.lang 3.x ) - від Apache Commons .


... а звідки беруться StringUtils?
vwegert

1
Ах, хороший пункт. Пройшло деякий час, коли я переглянув цей код, але я вважаю, що ми використовували org.apache.commons.lang.StringUtils.
Псалом Огр33,

Ось пряме посилання на метод приєднання StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S

2
Приємно, дякую. StringUtils # join також працює на Iterable, тому, ймовірно, не потрібно спочатку конвертувати свою колекцію в масив.
Рой

47

Я написав цю петлю:

StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
    buff.append(sep);
    buff.append(str);
    sep = ",";
}
return buff.toString();

Не турбуйтеся про продуктивність сеп. Завдання дуже швидко. Точка, як правило, знімає першу ітерацію циклу (так як це часто доводиться стикатися з дивацтвами, такими як нульові та моно / біморфні чекі).

Якщо ви використовуєте його багато (не раз), покладіть його на спільний метод.

Існує ще одне питання щодо stackoverflow, який стосується того, як вставити список ідентифікаторів у оператор SQL.


42

Оскільки Java 8, ви можете використовувати:


3
Це добре! Якщо ви маніпулюєте об'єктами, які потребують спеціального перетворення рядка, не охопленого toString (), замініть Object :: toString на java.util.function.Function <YourType, String>, який відображає ваш клас у String.
Торбен

2
Також ви можете використовувати його так: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));для однієї змінної зі своєї колекції.
numsu

Цікаво про те, наскільки це stream. Для int [] або long [] або інших масивів, де значення може бути просто передано String, я б шукав рішення, яке не передається потоком. Насправді я дивлюсь.
Адам

11

Я вважав ідіому ітератора елегантною, тому що в ній є тест на більшу кількість елементів (умітний нульовий / порожній тест на стислість):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}

... і, мабуть, менш ефективно, ніж прийняте рішення, залежно від того, наскільки складний виклик 'hasNext ()'. Крім того, ви, ймовірно, повинні використовувати StringBuilder, а не зв'язок String.
Стівен С

Гаразд, якщо ви хочете бути вибагливими щодо ефективності, використовуйте StringWriter;)
Мігель Пінг,

8

Є багато ручних рішень для цього, але я хотів ще раз повторити та оновити відповідь Джулі. Використовуйте столярний клас колекцій Google .

Joiner.on(", ").join(34, 26, ..., 2)

Він обробляє var arg, ітерабелі та масиви і належним чином обробляє роздільники з більш ніж одного знаку (на відміну від відповіді gimmel). Він також буде обробляти нульові значення у вашому списку, якщо вам це потрібно.


7

Ось неймовірно загальна версія, яку я створив із поєднання попередніх пропозицій:

public static <T> String buildCommaSeparatedString(Collection<T> values) {
    if (values==null || values.isEmpty()) return "";
    StringBuilder result = new StringBuilder();
    for (T val : values) {
        result.append(val);
        result.append(",");
    }
    return result.substring(0, result.length() - 1);
}

7
String.join(", ", collectionOfStrings)

доступний у Java8 api.

альтернатива (без необхідності додавати залежність google guava):

Joiner.on(",").join(collectionOfStrings);

5

Ви можете спробувати

List collections = Arrays.asList(34, 26, "...", 2);
String asString = collection.toString();
// justValues = "34, 26, ..., 2"
String justValues = asString.substring(1, asString.length()-1);

4

Це буде найкоротшим рішенням поки що, за винятком використання Guava або Apache Commons

String res = "";
for (String i : values) {
    res += res.isEmpty() ? i : ","+i;
}

Добре з 0,1 та n списком елементів. Але вам потрібно буде перевірити наявність нульового списку. Я використовую це в GWT, тому мені добре без StringBuilder там. А для коротких списків із лише парою елементів це нормально і в іншому місці;)


4

У випадку, якщо хтось натрапив на це у більш пізні часи, я додав простий варіант за допомогою Java 8 reduce(). Вона також включає деякі згадані рішення іншими:

import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.StringUtils;    

import com.google.common.base.Joiner;

public class Dummy {
  public static void main(String[] args) {

    List<String> strings = Arrays.asList("abc", "de", "fg");
    String commaSeparated = strings
        .stream()
        .reduce((s1, s2) -> {return s1 + "," + s2; })
        .get();

    System.out.println(commaSeparated);

    System.out.println(Joiner.on(',').join(strings));

    System.out.println(StringUtils.join(strings, ","));

  }
}


4

Я думаю, що це не дуже гарна ідея збільшити sql, що об'єднує значення пункту, як ви робите:

SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)

Звідки valueXвиходить список рядків.

По-перше, якщо ви порівнюєте рядки, вони повинні бути цитовані, це не є тривіальним, якщо у рядків може бути цитата всередині.

По-друге, якщо значення надходять від користувача або іншої системи, можлива атака ін'єкції SQL.

Це набагато більше багатослівного, але те, що ви повинні зробити, це створити такий рядок:

SELECT.... FROM.... WHERE ID IN( ?, ?,....?)

а потім зв’яжіть змінні Statement.setString(nParameter,parameterValue).


3

Просто ще один метод боротьби з цією проблемою. Не найкоротший, але він ефективний і виконує роботу.

/**
 * Creates a comma-separated list of values from given collection.
 * 
 * @param <T> Value type.
 * @param values Value collection.
 * @return Comma-separated String of values.
 */
public <T> String toParameterList(Collection<T> values) {
   if (values == null || values.isEmpty()) {
      return ""; // Depending on how you want to deal with this case...
   }
   StringBuilder result = new StringBuilder();
   Iterator<T> i = values.iterator();
   result.append(i.next().toString());
   while (i.hasNext()) {
      result.append(",").append(i.next().toString());
   }
   return result.toString();
}

2

Є деякі сторонні бібліотеки Java, які надають метод з'єднання рядків, але ви, мабуть, не хочете починати використовувати бібліотеку лише для чогось такого простого. Я б просто створив подібний хелперний метод, який, на мою думку, трохи кращий, ніж ваша версія. Він використовує StringBuffer, який буде більш ефективним, якщо вам потрібно приєднатися до багатьох рядків, і він працює над колекцією будь-якого типу.

public static <T> String join(Collection<T> values)
{
    StringBuffer ret = new StringBuffer();
    for (T value : values)
    {
        if (ret.length() > 0) ret.append(",");
        ret.append(value);
    }
    return ret.toString();
}

Ще одна пропозиція щодо використання Collection.toString () є коротшою, але вона покладається на те, що Collection.toString () повертає рядок у дуже специфічному форматі, на який я особисто не хотів би покладатися.


2

Якщо ви використовуєте Spring, ви можете:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(пакет org.springframework.util)


1

Я не впевнений, наскільки це «витончено», але це, звичайно, трохи коротше. Він буде працювати з різними різними типами колекції, наприклад Set <Integer>, List <String> тощо.

public static final String toSqlList(Collection<?> values) {

    String collectionString = values.toString();

    // Convert the square brackets produced by Collection.toString() to round brackets used by SQL
    return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}

Вправа для читача : модифікуйте цей метод, щоб він правильно обробляв нульову / порожню колекцію :)


1

Що робить код некрасивим - це спеціальна обробка для першого випадку. Більшість рядків у цьому маленькому фрагменті присвячені не виконанню рутинної роботи коду, а обробці цього особливого випадку. І ось що вирішує альтернативи, як Gimel, переміщуючи спеціальну обробку поза петлею. Є один особливий випадок (ну, ви можете побачити і початок, і кінець як особливі випадки, але лише один з них потрібно обробляти спеціально), тому обробляти його всередині циклу зайво складно.


1

Я щойно зареєстрував тест на долар своєї бібліотеки :

@Test
public void join() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    String string = $(list).join(",");
}

він створює плавну обгортку навколо списків / масивів / рядків / тощо, використовуючи лише один статичний імпорт :$ .

NB :

використовуючи діапазони, попередній список можна переписати як $(1, 5).join(",")


1

Приємне в виразі IN полягає в тому, що якщо ви повторили значення, це не змінює результат. Отже, просто дублюйте перший пункт і обробіть весь список. Це передбачає, що в списку є хоча б один пункт. Якщо немає елементів, я б радив перевірити це спочатку, а потім взагалі не виконувати SQL.

Це зробить трюк, очевидно, що він робить, і не покладається на жодні зовнішні бібліотеки:

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
  inString.append(",").append(currentID);
}

1

Хоча я вважаю, що найкраще скористатися столярником від Guava, якби я кодував це вручну, я вважаю такий підхід більш елегантним, ніж прапор "першого" або відсікання останньої коми.

private String commas(Iterable<String> strings) {
    StringBuilder buffer = new StringBuilder();
    Iterator<String> it = strings.iterator();
    if (it.hasNext()) {
        buffer.append(it.next());
        while (it.hasNext()) {
            buffer.append(',');
            buffer.append(it.next());
        }
    }

    return buffer.toString();
}


1

Ще один варіант, заснований на тому, що я бачу тут (з незначними модифікаціями).

public static String toString(int[] numbers) {
    StringBuilder res = new StringBuilder();
    for (int number : numbers) {
        if (res.length() != 0) {
            res.append(',');
        }
        res.append(number);
    }
    return res.toString();
}

1

"Методи" приєднання доступні в масивах та класах, які розширюють, AbstractCollectionsале не перекривають toString()метод (як практично у всіх колекціях у java.util).

Наприклад:

String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there

Це досить дивний спосіб, оскільки він працює лише для чисел, схожих на дані SQL.


1
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");

StringUtils від springframeowrk: spring-core



0
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));

$~(Hola, Julio)

1
Це погана практика. Ви не можете зробити припущення, що реалізація toString змінюється.
drindt

0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

Представлення рядків складається із списку елементів колекції у порядку їх повернення ітератором, укладеним у квадратні дужки ("[]"). Суміжні елементи розділені символами "," (кома та пробіл).

AbstractCollection javadoc


0

Маркер списку = новий ArrayList (результат); остаточний StringBuilder builder = новий StringBuilder ();

    for (int i =0; i < tokens.size(); i++){
        builder.append(tokens.get(i));
        if(i != tokens.size()-1){
            builder.append(TOKEN_DELIMITER);
        }
    }

builder.toString ();

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.