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


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

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


Предположительно, приведенный выше 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 .


... а откуда берется 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.


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

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

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's, перемещая специальную обработку за пределы цикла. Есть один особый случай (ну, вы можете видеть и начало, и конец как особые случаи, но только один из них требует особой обработки), поэтому обработка его внутри цикла излишне сложна.


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

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

Другой вариант, основанный на том, что я здесь вижу (с небольшими изменениями).

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 = новый 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.