Во время работы с базами данных я заметил, что пишу строки запроса, и в этих строках я должен поставить несколько ограничений в предложение 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-запрос) усложняются.
Какой у вас (более) элегантный образ?
Есть сложная часть, когда сами элементы списка (строки) содержат запятые или двойные кавычки, и их нужно экранировать кавычками. Если я ничего не пропустил, приведенные выше примеры не учитывают это, и мне не нравится идея перебирать все тексты и искать запятые .. Как вы думаете, есть лучший способ решить эту проблему?
проверьте этот ответ ... stackoverflow.com/a/15815631/728610
Вы когда-нибудь проверяли stackoverflow.com/questions/10850753/…?
Это должно сработать. stackoverflow.com/a/15815631/3157062




Я не уверен, насколько это «изощренно», но определенно немного короче. Он будет работать с различными типами коллекций, например. Установите <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): "";
Обратите внимание, что для этого в вашей коллекции должен быть хотя бы один элемент.
Посмотреть ответ с наибольшим количеством голосов - code.google.com/p/guava-libraries/wiki/StringsExplained
См. Предлагаемое исправление для пустого списка.
ответ гуавы лучше. не нужно изобретать велосипед.
@davidjnelson, возможно, ответ гуавы лучше на практике, но этот показывает, как это действительно работает. Кстати, @gimel, я думаю, вам здесь StringBuilder вообще не нужен. Вы можете использовать обычный String и заменить метод appender на +=.
@ xtreme-biker При достаточно современном компиляторе StringBuilder может использоваться автоматически. Перед использованием + = проверьте свою среду. См. stackoverflow.com/questions/1532461/…
Для повышения производительности рекомендуется добавлять запятую как символ, а не как строку: используйте result.append (','); вместо использования result.append (",");
Не нравится такая практика формирования неправильного результата (избыточный конечный разделитель) и последующего «исправления» - это очень сложный и рецепт ошибок разработчика / кодирования. Лучше использовать логический флаг «первый» или ответ Тома Хотина stackoverflow.com/a/205976/768795.
Худшая часть этого ответа - всегда добавлять общее, а затем удалять последнее. Guava Joiner делает почти то же самое, что и этот код, но использует начальный if для условного цикла по остальным элементам и добавления запятых перед каждым (а не после).
Я думаю, что не стоит создавать 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?
Ах, хорошее замечание. Я давно не смотрел этот код, но мне кажется, что мы использовали org.apache.commons.lang.StringUtils.
Вот живая ссылка на метод соединения StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/ org /…
Хорошо, спасибо. StringUtils # join также работает с Iterable, поэтому, вероятно, нет необходимости сначала преобразовывать вашу коллекцию в массив.
Я пишу этот цикл следующим образом:
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 /…
А сегодня Коллекции устарели. Вместо этого используйте Google Guava.
Между тем org.apache.commons.lang.StringUtils остается неизменным. :-)
гуава переместилась. см. github.com/google/guava/wiki/StringsExplained
Еще один способ справиться с этой проблемой. Не самый короткий, но эффективный и выполняет свою работу.
/**
* 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, а не объединение строк.
Хорошо, если вы хотите быть придирчивым к эффективности, используйте StringWriter;)
Вы можете использовать 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 изменится.
Если вы используете 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.
Кроме того, вы можете использовать его так: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(",")); для одной переменной из вашей коллекции.
Интересно, как работает stream. Для int [] или long [] или других массивов, где значение может быть просто приведено к String, я бы поискал решение без потоковой передачи. Собственно ищу.
Есть простой способ. Вы можете получить результат в одной строке.
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
Предположительно, приведенный выше SQL должен выглядеть так: select * from customer, где customer.id in (34, 26, 2);