A maneira mais sofisticada de criar Strings separadas por vírgulas a partir de uma Coleção / Matriz / Lista?

98

Durante meu trabalho com bancos de dados, percebi que escrevo strings de consulta e, nessas strings, tenho que colocar várias restrições na cláusula where de uma lista / array / coleção. Deve ser assim:

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

Você pode simplificar isso, reduzindo isso à questão de que você tem uma coleção de strings e deseja criar uma lista separada por vírgulas dessas strings em apenas uma string.

Minha abordagem que usei até agora é mais ou menos assim:

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

Mas isso é como você pode ver muito feio. Você não pode ver o que acontece lá à primeira vista, especialmente quando as strings construídas (como toda consulta SQL) estão ficando complicadas.

Qual é o seu jeito (mais) elegante?

maerch
fonte
Presumivelmente, o SQL mostrado acima deveria se parecer com isto: select * from customer where customer.id in (34, 26, 2);
Dónal de
Existe uma parte complicada, quando os próprios itens de lista (strings) contêm vírgulas ou aspas duplas e precisam ser escapados com aspas. Se não perdi nada, os exemplos acima não consideram e odeio a ideia de percorrer todos os textos e procurar vírgulas. Acha que existe uma maneira melhor de resolver isto?
Samurai Girl
verifique esta resposta ... stackoverflow.com/a/15815631/728610
Arvind Sridharan
Você já consultou stackoverflow.com/questions/10850753/… ?
Hiren Patel
Isso deve bastar. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Respostas:

85

Nota: Esta resposta era boa quando foi escrita 11 anos atrás, mas agora existem opções muito melhores para fazer isso de forma mais limpa em uma única linha, usando apenas classes integradas do Java ou usando uma biblioteca de utilitários. Veja outras respostas abaixo.


Como as strings são imutáveis, você pode usar a classe StringBuilder se for alterar a String no código.

A classe StringBuilder pode ser vista como um objeto String mutável que aloca mais memória quando seu conteúdo é alterado.

A sugestão original na pergunta pode ser escrita de forma ainda mais clara e eficiente, cuidando da vírgula final redundante :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";
gimel
fonte
7
Observe que isso requer que sua coleção tenha pelo menos um elemento.
Guus
3
Veja a resposta mais votada - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel
Veja a correção sugerida para uma lista vazia.
gimel
1
a resposta da goiaba é melhor. Não há necessidade de reinventar a roda.
davidjnelson
1
@ xtreme-biker Com um compilador razoavelmente moderno, StringBuilder pode ser usado automaticamente. Verifique seu ambiente antes de usar + =. Consulte stackoverflow.com/questions/1532461/…
gimel
89

Use o Google Guava API 's joinmétodo:

Joiner.on(",").join(collectionOfStrings);
Julie
fonte
4
Hoje em dia a aula é chamada Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik
2
E hoje, as coleções estão obsoletas. Em vez disso, use o Google Guava .
darioo,
12
Enquanto isso, org.apache.commons.lang.StringUtils permanece inalterado. :-)
Ogre Salm33
1
goiaba mudou. consulte github.com/google/guava/wiki/StringsExplained
gimel
76

Acabei de ver o código que fez isso hoje. Esta é uma variação da resposta de AviewAnew.

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

O StringUtils (<- commons.lang 2.x ou link commons.lang 3.x ) que usamos é do Apache Commons .

Ogre Salmo 33
fonte
... e de onde vem o StringUtils?
vwegert
1
Ah, bom ponto. Já faz um tempo que olhei para esse código, mas acredito que estávamos usando org.apache.commons.lang.StringUtils.
Ogre Salmo 33 de
Aqui está um link ao vivo para o método de junção de StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S
2
Legal, obrigado. StringUtils # join também funciona em um Iterable, então provavelmente não há necessidade de converter sua coleção em um array primeiro.
Roy
47

A maneira como escrevo esse loop é:

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

Não se preocupe com o desempenho do sep. Uma tarefa é muito rápida. O ponto de acesso tende a descascar a primeira iteração de um loop de qualquer maneira (já que freqüentemente tem que lidar com estranhezas, como verificações inlining nulas e mono / bimórficas).

Se você usar muito (mais de uma vez), coloque-o em um método compartilhado.

Há outra questão sobre stackoverflow que trata de como inserir uma lista de ids em uma instrução SQL.

Tom Hawtin - tackline
fonte
42

Desde o Java 8, você pode usar:

Abdull
fonte
3
Isso é bom! Se você estiver manipulando objetos que precisam de uma conversão de string especial não coberta por toString (), substitua Object :: toString por um java.util.function.Function <YourType, String> que mapeia sua classe para uma String.
Torben,
2
Além disso, você pode usá-lo assim: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));para uma única variável de sua coleção.
numsu
Eu me pergunto sobre o desempenho disso stream. Para int [] ou long [] ou outras matrizes onde o valor pode simplesmente ser convertido String, eu procuraria uma solução sem streaming. Na verdade, estou procurando.
Adam
11

Achei o idioma do iterador elegante, porque ele tem um teste para mais elementos (teste nulo / vazio omitido por brevidade):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}
Miguel Ping
fonte
... e provavelmente menos eficiente que a solução aceita, dependendo de quão complexa é a chamada 'hasNext ()'. Além disso, você provavelmente deve usar um StringBuilder em vez de concatenação de String.
Stephen C
OK, se você quiser ser exigente quanto à eficiência, use um StringWriter;)
Miguel Ping
8

Existem muitas soluções manuais para isso, mas eu queria reiterar e atualizar a resposta de Julie acima. Use a classe Joiner das coleções do google .

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

Ele lida com var args, iterables e arrays e lida corretamente com separadores de mais de um char (ao contrário da resposta do gimmel). Ele também tratará valores nulos em sua lista, se necessário.

caso nelson
fonte
7

Esta é uma versão incrivelmente genérica que construí a partir de uma combinação das sugestões anteriores:

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);
}
Jeff
fonte
7
String.join(", ", collectionOfStrings)

disponível na API Java8.

alternativa para (sem a necessidade de adicionar uma dependência do google goiaba):

Joiner.on(",").join(collectionOfStrings);
robjwilkins
fonte
5

Você poderia tentar

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

Esta será a solução mais curta até agora, exceto pelo uso de Guava ou Apache Commons

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

Bom com 0,1 en lista de elementos. Mas você precisará verificar a lista de nulos. Eu uso isso no GWT, então estou bem sem StringBuilder lá. E para listas curtas com apenas alguns elementos também está ok em outro lugar;)

eu pego
fonte
4

Caso alguém tenha tropeçado nisso recentemente, adicionei uma variação simples usando Java 8 reduce(). Também inclui algumas das soluções já mencionadas por outros:

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, ","));

  }
}
Christof
fonte
4

No Android, você deve usar isto:

TextUtils.join(",",collectionOfStrings.toArray());
Pascalius
fonte
4

Acho que não é uma boa ideia construir o sql concatenando os valores da cláusula where como você está fazendo:

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

De onde valueXvem uma lista de Strings.

Primeiro, se você está comparando Strings, elas devem ser citadas, e isso não é trivial se as Strings pudessem ter uma citação dentro.

Em segundo lugar, se os valores vierem do usuário ou de outro sistema, um ataque de injeção de SQL é possível.

É muito mais detalhado, mas o que você deve fazer é criar uma String como esta:

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

e vincule as variáveis ​​com Statement.setString(nParameter,parameterValue).

Telcontar
fonte
3

Apenas mais um método para lidar com esse problema. Não é o mais curto, mas é eficiente e faz o trabalho.

/**
 * 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
fonte
2

Existem algumas bibliotecas Java de terceiros que fornecem método de junção de string, mas você provavelmente não quer começar a usar uma biblioteca apenas para algo simples como isso. Eu apenas criaria um método auxiliar como este, que acho um pouco melhor do que a sua versão, ele usa StringBuffer, que será mais eficiente se você precisar juntar muitas strings, e funciona em uma coleção de qualquer tipo.

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

Outra sugestão com o uso de Collection.toString () é mais curta, mas depende de Collection.toString () retornar uma string em um formato muito específico, no qual eu pessoalmente não gostaria de confiar.

Denis Fradlin
fonte
2

Se você usa Spring, pode fazer:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(pacote org.springframework.util)

semana
fonte
1

Não tenho certeza de quão "sofisticado" isso é, mas certamente é um pouco mais curto. Funcionará com vários tipos diferentes de coleção, por exemplo, Set <Integer>, List <String>, etc.

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

Exercício para o leitor : modifique este método para que ele lide corretamente com uma coleção nula / vazia :)

Dónal
fonte
1

O que torna o código feio é o tratamento especial para o primeiro caso. A maioria das linhas neste pequeno trecho é dedicada, não a fazer o trabalho de rotina do código, mas a lidar com esse caso especial. E é isso que alternativas como o gimel resolve, movendo o tratamento especial para fora do loop. Há um caso especial (bem, você pode ver o início e o fim como casos especiais - mas apenas um deles precisa ser tratado de maneira especial), portanto, manipulá-lo dentro do loop é desnecessariamente complicado.

Carl Manaster
fonte
1

Acabei de fazer o check-in de um teste para o dinheiro da minha biblioteca :

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

ele cria um wrapper fluente em torno de listas / matrizes / strings / etc usando apenas uma importação estática :$ .

NB :

usando intervalos, a lista anterior pode ser reescrita como $(1, 5).join(",")

dfa
fonte
1

O bom da expressão IN é que, se você repetir os valores, o resultado não será alterado. Portanto, basta duplicar o primeiro item e processar toda a lista. Isso pressupõe que haja pelo menos um item na lista. Se não houver nenhum item, sugiro verificar primeiro e, em seguida, não executar o SQL.

Isso vai funcionar, é óbvio no que está fazendo e não depende de nenhuma biblioteca externa:

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

Embora eu ache que sua melhor aposta é usar o Joiner do Guava, se eu fosse codificá-lo manualmente, acho essa abordagem mais elegante do que o sinalizador 'primeiro' ou cortar a última vírgula.

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

se você tem uma matriz, pode fazer:

Arrays.asList(parameters).toString()
Tempo nublado
fonte
1

Outra opção, com base no que vejo aqui (com pequenas modificações).

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
fonte
1

Os 'métodos' de junção estão disponíveis em Arrays e nas classes que estendem, AbstractCollectionsmas não substituem o toString()método (como praticamente todas as coleções em java.util).

Por exemplo:

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

Essa é uma maneira bastante estranha, pois funciona apenas para números semelhantes, dados SQL.

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

StringUtils de springframeowrk: spring-core

Sridhar
fonte
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)
Julio Cesar
fonte
1
Esta é uma prática ruim. Você não pode supor que a implementação de toString muda.
Drindt de
0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

A representação da string consiste em uma lista dos elementos da coleção na ordem em que são retornados por seu iterador, entre colchetes ("[]"). Os elementos adjacentes são separados pelos caracteres "," (vírgula e espaço).

AbstractCollection javadoc

Todd Gatts
fonte
0

Lista de token = novo ArrayList (resultado); construtor final StringBuilder = new 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
fonte