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

Во время работы с базами данных я заметил, что пишу строки запроса, и в этих строках я должен поставить несколько ограничений в предложение 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);

Dónal 15.10.2008 21:27

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

Samurai Girl 01.02.2012 16:18

проверьте этот ответ ... stackoverflow.com/a/15815631/728610

Arvind Sridharan 16.08.2013 12:52

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

Hiren Patel 13.03.2015 13:30

Это должно сработать. stackoverflow.com/a/15815631/3157062

Parag Jadhav 09.07.2016 11:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
98
5
145 247
32
Перейти к ответу Данный вопрос помечен как решенный

Ответы 32

Я не уверен, насколько это «изощренно», но определенно немного короче. Он будет работать с различными типами коллекций, например. Установите <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) + ")";
}

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

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

Ответ принят как подходящий

Примечание. Эти ответы были хорошими, когда они были написаны 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): "";

Обратите внимание, что для этого в вашей коллекции должен быть хотя бы один элемент.

Guus 21.12.2010 16:59

Посмотреть ответ с наибольшим количеством голосов - code.google.com/p/guava-libraries/wiki/StringsExplained

gimel 10.03.2012 14:23

См. Предлагаемое исправление для пустого списка.

gimel 21.06.2012 09:07

ответ гуавы лучше. не нужно изобретать велосипед.

davidjnelson 15.11.2012 23:34

@davidjnelson, возможно, ответ гуавы лучше на практике, но этот показывает, как это действительно работает. Кстати, @gimel, я думаю, вам здесь StringBuilder вообще не нужен. Вы можете использовать обычный String и заменить метод appender на +=.

Xtreme Biker 15.03.2013 12:56

@ xtreme-biker При достаточно современном компиляторе StringBuilder может использоваться автоматически. Перед использованием + = проверьте свою среду. См. stackoverflow.com/questions/1532461/…

gimel 15.03.2013 13:46

Для повышения производительности рекомендуется добавлять запятую как символ, а не как строку: используйте result.append (','); вместо использования result.append (",");

bolei 06.05.2016 00:25

Не нравится такая практика формирования неправильного результата (избыточный конечный разделитель) и последующего «исправления» - это очень сложный и рецепт ошибок разработчика / кодирования. Лучше использовать логический флаг «первый» или ответ Тома Хотина stackoverflow.com/a/205976/768795.

Thomas W 09.06.2017 03:51

Худшая часть этого ответа - всегда добавлять общее, а затем удалять последнее. Guava Joiner делает почти то же самое, что и этот код, но использует начальный if для условного цикла по остальным элементам и добавления запятых перед каждым (а не после).

Samuel Neff 04.01.2020 00:04

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

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

Где valueX происходит из списка строк.

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

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

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

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

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

Я только что посмотрел на код, который сделал это сегодня. Это вариант ответа AviewAnew.

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

StringUtils (<- commons.lang 2.x или commons.lang 3.x ссылка), который мы использовали, взят из Apache Commons.

... а откуда берется StringUtils?

vwegert 19.09.2010 21:18

Ах, хорошее замечание. Я давно не смотрел этот код, но мне кажется, что мы использовали org.apache.commons.lang.StringUtils.

Ogre Psalm33 20.09.2010 22:28

Вот живая ссылка на метод соединения StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/‌ org /…

Ryan S 15.06.2014 01:52

Хорошо, спасибо. StringUtils # join также работает с Iterable, поэтому, вероятно, нет необходимости сначала преобразовывать вашу коллекцию в массив.

Roy 21.04.2015 10: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.

Используйте метод joinAPI Google Guava:

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

В настоящее время класс называется Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/goog‌ le /…

Jonik 05.11.2009 01:10

А сегодня Коллекции устарели. Вместо этого используйте Google Guava.

darioo 07.10.2011 18:13

Между тем org.apache.commons.lang.StringUtils остается неизменным. :-)

Ogre Psalm33 28.03.2012 23:42

гуава переместилась. см. github.com/google/guava/wiki/StringsExplained

gimel 03.08.2016 19:08

Еще один способ справиться с этой проблемой. Не самый короткий, но эффективный и выполняет свою работу.

/**
 * 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();
}

Я нашел идиому итератора элегантной, потому что в ней есть тест на большее количество элементов (для краткости опущен нулевой / пустой тест):

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 24.07.2009 03:06

Хорошо, если вы хотите быть придирчивым к эффективности, используйте StringWriter;)

Miguel Ping 14.07.2011 20:48

Вы можете использовать LINQ (to SQL), и вы можете использовать образец LINQ динамического запроса от MS. http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Вы могли бы попробовать

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

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

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

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

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

«Методы» соединения доступны в массивах и классах, расширяющих 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.

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

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

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

NB:

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

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

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

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

Вот невероятно общая версия, которую я создал из комбинации предыдущих предложений:

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);
}

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)

Это плохая практика. Вы не можете сделать предположение, что реализация toString изменится.

drindt 19.08.2015 11:27

Если вы используете Spring, вы можете:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

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

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

На данный момент это будет самое короткое решение, за исключением использования Guava или Apache Commons.

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

Хорошо с 0,1 и n списком элементов. Но вам нужно будет проверить нулевой список. Я использую это в GWT, так что мне хорошо там без StringBuilder. А для коротких списков всего с парой элементов тоже нормально;)

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

Arrays.asList(parameters).toString()

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

The string representation consists of a list of the collection's elements in the order they are returned by its iterator, enclosed in square brackets ("[]"). Adjacent elements are separated by the characters ", " (comma and space).

AbstractCollection javadoc

Токен списка = новый 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 ();

String.join(", ", collectionOfStrings)

доступно в Java8 api.

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

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

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

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();
}

На случай, если кто-то в последнее время наткнулся на это, я добавил простой вариант с использованием 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, ","));

  }
}

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

TextUtils.join(",",collectionOfStrings.toArray());

Начиная с Java 8 вы можете использовать:

  • String String.join(CharSequence delimiter, CharSequence... elements)
  • String String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
  • Если вы хотите взять не-String и присоединить их к String, вы можете использовать Collectors.joining(CharSequence delimiter), например:

    String joined = anyCollection.stream().map(Object::toString).collect(Collectors.joining(","));

Это хорошо! Если вы управляете объектами, которым требуется специальное преобразование строк, не охватываемое toString (), замените Object :: toString на java.util.function.Function <YourType, String>, который отображает ваш класс на String.

Torben 15.10.2015 09:54

Кроме того, вы можете использовать его так: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(",")); для одной переменной из вашей коллекции.

numsu 18.08.2016 11:16

Интересно, как работает stream. Для int [] или long [] или других массивов, где значение может быть просто приведено к String, я бы поискал решение без потоковой передачи. Собственно ищу.

Adam 06.11.2017 15:11

Есть простой способ. Вы можете получить результат в одной строке.

String memberIdsModifiedForQuery = memberIds.toString().replace("[", "(").replace("]", ")");

Чтобы получить полное представление, проверьте код ниже

 public static void main(String[] args) {       
        List<Integer>memberIds=new ArrayList<Integer>();  //This contain member ids we want to process
        //adding some sample values for example
        memberIds.add(3); 
        memberIds.add(4);
        memberIds.add(2);
        String memberIdsModifiedForQuery = memberIds.toString().replace("[", "(").replace("]", ")"); //here you will get (3,4,5) That you can directly use in query
        System.out.println(memberIdsModifiedForQuery);
        String exampleQuery = "select * from customer where customer.id in "+memberIdsModifiedForQuery+" ";
    }

Самый простой способ в Android преобразовать список в строку, разделенную запятыми, - это использовать android.text.TextUtils

 ArrayList<String>Myli = new ArrayList<String>();
 String ArayCommase=android.text.TextUtils.join(",", Myli);

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

StringUtils из springframeowrk: spring-core

Другие вопросы по теме