Самый изощренный способ создания разделенных запятыми строк из коллекции / массива / списка?

98

Во время работы с базами данных я заметил, что пишу строки запроса, и в этих строках я должен поставить несколько ограничений в предложение where из списка / массива / коллекции. Должно получиться так:

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) усложняются.

Какой у вас (более) элегантный образ?

maerch
источник
Предположительно, приведенный выше SQL должен выглядеть так: select * from customer, где customer.id in (34, 26, 2);
Донал,
Есть сложная часть, когда сами элементы (строки) списка содержат запятые или двойные кавычки, и их нужно экранировать кавычками. Если я ничего не пропустил, приведенные выше примеры не учитывают это, и мне не нравится идея перебирать все тексты и искать запятые .. Как вы думаете, есть лучший способ решить эту проблему?
Samurai Girl
проверьте этот ответ ... stackoverflow.com/a/15815631/728610
Арвинд Шридхаран
Вы когда-нибудь проверяли stackoverflow.com/questions/10850753/… ?
Hiren Patel,
Это должно сработать. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Ответы:

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
Обратите внимание, что для этого в вашей коллекции должен быть хотя бы один элемент.
Guus
3
См топ проголосовали ответ - code.google.com/p/guava-libraries/wiki/StringsExplained
Гимел
См. Предлагаемое исправление для пустого списка.
gimel
1
ответ гуавы лучше. не нужно изобретать велосипед.
davidjnelson
1
@ xtreme-biker При достаточно современном компиляторе StringBuilder может использоваться автоматически. Перед использованием + = проверьте свою среду. См. Stackoverflow.com/questions/1532461/…
gimel
89

Используйте Google гуавы API «сек joinметод:

Joiner.on(",").join(collectionOfStrings);
Джули
источник
4
В настоящее время класс называется Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik,
2
А сегодня Коллекции устарели. Вместо этого используйте Google Guava .
darioo 07
12
Между тем org.apache.commons.lang.StringUtils остается без изменений. :-)
Ogre Psalm33
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 .

Огре Псалом 33
источник
... а откуда берется StringUtils?
vwegert
1
Ах, хорошее замечание. Я давно не смотрел этот код, но мне кажется, мы использовали org.apache.commons.lang.StringUtils.
Ogre Psalm33
Вот живая ссылка на метод соединения 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();

Не беспокойтесь о производительности sep. Задание выполняется очень быстро. Hotspot имеет тенденцию откладывать первую итерацию цикла в любом случае (поскольку ему часто приходится иметь дело с такими странностями, как нулевые и моно / биморфные проверки встраивания).

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

Есть еще один вопрос о stackoverflow, касающийся того, как вставить список идентификаторов в оператор SQL.

Том Хотин - tackline
источник
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, а не объединение строк.
Stephen C
Хорошо, если вы хотите быть придирчивым к эффективности, используйте StringWriter;)
Мигель Пинг
8

Для этого есть много ручных решений, но я хотел повторить и обновить ответ Джули выше. Используйте Google Collections Joiner class .

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

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

дело Нельсон
источник
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

В Android вы должны использовать это:

TextUtils.join(",",collectionOfStrings.toArray());
Паскалий
источник
4

Я думаю, что это не лучшая идея создавать sql, объединяющий значения предложения where, как вы:

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

Откуда valueXберется из списка строк.

Во-первых, если вы сравниваете строки, они должны быть заключены в кавычки, и это нетривиально, если строки могут содержать кавычки внутри.

Во-вторых, если значения поступают от пользователя или другой системы, то возможна атака с использованием SQL-инъекции.

Он намного более подробный, но вам следует создать такую ​​строку:

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

а затем свяжите переменные с Statement.setString(nParameter,parameterValue) .

Telcontar
источник
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();
}
Silverminken
источник
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)

Weekens
источник
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) + ")";
}

Упражнение для читателя : измените этот метод, чтобы он правильно обрабатывал пустую / пустую коллекцию :)

Dónal
источник
1

Что делает код уродливым, так это особая обработка первого случая. Большинство строк в этом небольшом фрагменте посвящены не выполнению рутинной работы кода, а обработке этого особого случая. И это то, что решают альтернативы, такие как gimel's, перемещая специальную обработку за пределы цикла. Есть один особый случай (ну, вы можете видеть и начало, и конец как особые случаи, но только один из них требует особой обработки), поэтому обработка его внутри цикла излишне сложна.

Карл Манастер
источник
1

Я только что зарегистрировал тест на свой библиотечный доллар :

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

это создать свободно обертку списков / массивы / строки / и т.д. , используя только один статический импорт : $.

NB :

используя диапазоны, предыдущий список можно переписать как $(1, 5).join(",")

DFA
источник
1

Преимущество выражения IN заключается в том, что если у вас есть повторяющиеся значения, это не меняет результат. Итак, просто продублируйте первый элемент и обработайте весь список. Предполагается, что в списке есть хотя бы один элемент. Если элементов нет, я бы предложил сначала проверить это, а затем вообще не выполнять SQL.

Это поможет, очевидно, в том, что он делает, и не зависит от каких-либо внешних библиотек:

StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
  inString.append(",").append(currentID);
}
VIM
источник
1

Хотя я думаю, что лучше всего использовать Joiner из 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

если у вас есть массив, вы можете:

Arrays.asList(parameters).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();
}
Elcuco
источник
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.

xss
источник
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 = новый 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 ();

UPS
источник