Cara paling canggih untuk membuat String yang dipisahkan koma dari Koleksi / Larik / Daftar?

98

Selama pekerjaan saya dengan database, saya perhatikan bahwa saya menulis string kueri dan dalam string ini saya harus meletakkan beberapa batasan di klausa di mana dari daftar / larik / koleksi. Akan terlihat seperti ini:

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

Anda dapat menyederhanakan ini dengan menguranginya menjadi pertanyaan bahwa Anda memiliki kumpulan string dan ingin membuat daftar string ini dengan dipisahkan koma hanya dalam satu string.

Pendekatan yang saya gunakan sejauh ini adalah seperti itu:

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

Tapi ini seperti yang bisa Anda lihat sangat jelek. Anda tidak dapat melihat apa yang terjadi di sana pada tampilan pertama, terutama ketika string yang dibangun (seperti setiap kueri SQL) menjadi semakin rumit.

Apa cara Anda (yang lebih) elegan?

maerch
sumber
Agaknya SQL yang ditunjukkan di atas seharusnya terlihat seperti ini: pilih * dari pelanggan di mana customer.id di (34, 26, 2);
Dónal
Ada bagian yang sulit, ketika item daftar (string) itu sendiri berisi koma atau tanda kutip ganda dan mereka perlu di-escape dengan tanda kutip. Jika saya tidak melewatkan apa pun, contoh di atas tidak mempertimbangkannya dan saya benci gagasan mengulang-ulang semua teks dan mencari koma .. Apakah menurut Anda ada cara yang lebih baik untuk menyelesaikan ini?
Gadis Samurai
periksa jawaban ini ... stackoverflow.com/a/15815631/728610
Arvind Sridharan
Pernahkah Anda memeriksa stackoverflow.com/questions/10850753/… ?
Hiren Patel
Ini harus dilakukan. stackoverflow.com/a/15815631/3157062
Parag Jadhav

Jawaban:

85

Catatan: Jawaban ini bagus saat ditulis 11 tahun yang lalu, tetapi sekarang ada opsi yang jauh lebih baik untuk melakukannya dengan lebih rapi dalam satu baris, baik hanya menggunakan kelas bawaan Java atau menggunakan pustaka utilitas. Lihat jawaban lain di bawah.


Karena string tidak dapat diubah, Anda mungkin ingin menggunakan kelas StringBuilder jika Anda akan mengubah String dalam kode.

Kelas StringBuilder dapat dilihat sebagai objek String yang bisa berubah yang mengalokasikan lebih banyak memori saat kontennya diubah.

Saran asli dalam pertanyaan dapat ditulis dengan lebih jelas dan efisien, dengan menggunakan tanda koma yang berlebihan :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";
gimel
sumber
7
Perhatikan bahwa ini mengharuskan koleksi Anda memiliki setidaknya satu elemen.
Guus
3
Lihat jawaban pilihan teratas - code.google.com/p/guava-libraries/wiki/StringsExplained
gimel
Lihat perbaikan yang disarankan untuk daftar kosong.
gimel
1
jawaban jambu biji lebih baik. tidak perlu menemukan kembali roda.
davidjnelson
1
@ xtreme-biker Dengan kompiler yang cukup modern, StringBuilder dapat digunakan secara otomatis. Periksa lingkungan Anda sebelum menggunakan + =. Lihat stackoverflow.com/questions/1532461/…
gimel
89

Gunakan Google Jambu API 's joinmetode:

Joiner.on(",").join(collectionOfStrings);
Julie
sumber
4
Sekarang kelas itu disebut Joiner; google-collections.googlecode.com/svn/trunk/javadoc/com/google/…
Jonik
2
Dan hari ini, Koleksi tidak lagi digunakan. Gunakan Google Guava sebagai gantinya.
darioo
12
Sementara itu, org.apache.commons.lang.StringUtils tetap tidak berubah. :-)
Ogre Mazmur33
1
jambu biji sudah berpindah. lihat github.com/google/guava/wiki/StringsExplained
gimel
76

Saya baru saja melihat kode yang melakukan ini hari ini. Ini adalah variasi dari jawaban AviewAnew.

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

The StringUtils (<- 2.x commons.lang, atau link yang 3.x commons.lang ) kita digunakan adalah dari Apache Commons .

Ogre Mazmur33
sumber
... dan dari mana StringUtils berasal?
vwegert
1
Ah, poin yang bagus. Sudah lama sejak saya melihat kode itu, tapi saya yakin kami menggunakan org.apache.commons.lang.StringUtils.
Ogre Mazmur33
Berikut ini tautan langsung ke metode bergabung StringUtils commons.apache.org/proper/commons-lang/javadocs/api-release/org/…
Ryan S
2
Terima kasih banyak. StringUtils # join juga berfungsi pada Iterable, jadi mungkin tidak perlu mengonversi koleksi Anda ke array terlebih dahulu.
Roy
47

Cara saya menulis loop itu adalah:

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

Jangan khawatir tentang kinerja sep. Tugas sangat cepat. Hotspot cenderung mengelupas iterasi pertama dari sebuah loop (karena sering kali harus berurusan dengan keanehan seperti pemeriksaan sebaris null dan mono / bimorfik).

Jika Anda menggunakannya banyak (lebih dari sekali), masukkan ke dalam metode bersama.

Ada pertanyaan lain tentang stackoverflow yang berhubungan dengan cara memasukkan daftar id ke dalam pernyataan SQL.

Tom Hawtin - tackline
sumber
42

Sejak Java 8, Anda dapat menggunakan:

Abdull
sumber
3
Ini bagus! Jika Anda memanipulasi objek yang memerlukan konversi string khusus yang tidak tercakup oleh toString (), ganti Object :: toString dengan java.util.function.Function <YourType, String> yang memetakan kelas Anda ke String.
Torben
2
Selain itu, Anda dapat menggunakannya seperti ini: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));untuk variabel tunggal dari koleksi Anda.
numsu
Saya bertanya-tanya tentang kinerja itu stream. Untuk int [] atau long [] atau array lain di mana nilainya dapat dengan mudah ditransmisikan String, saya akan mencari solusi non-streaming. Sebenarnya saya sedang mencari.
Adam
11

Saya menemukan idiom iterator elegan, karena memiliki tes untuk lebih banyak elemen (tes kosong / kosong dihilangkan untuk singkatnya):

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
sumber
... dan mungkin kurang efisien daripada solusi yang diterima, bergantung pada seberapa kompleks panggilan 'hasNext ()'. Selain itu, Anda mungkin harus menggunakan StringBuilder daripada rangkaian String.
Stephen C
Oke, jika Anda ingin pilih-pilih tentang efisiensi, gunakan StringWriter;)
Miguel Ping
8

Ada banyak solusi manual untuk ini, tetapi saya ingin mengulangi dan memperbarui jawaban Julie di atas. Gunakan koleksi google Joiner class .

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

Ini menangani var args, iterable dan array dan dengan benar menangani pemisah lebih dari satu karakter (tidak seperti jawaban gimmel). Ini juga akan menangani nilai null dalam daftar Anda jika Anda membutuhkannya.

kasus nelson
sumber
7

Ini adalah versi yang sangat umum yang saya buat dari kombinasi saran sebelumnya:

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
sumber
7
String.join(", ", collectionOfStrings)

tersedia di api Java8.

alternatif untuk (tanpa perlu menambahkan ketergantungan google guava):

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

Kamu bisa mencoba

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
sumber
4

Ini akan menjadi solusi terpendek sejauh ini, kecuali menggunakan Guava atau Apache Commons

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

Baik dengan daftar elemen 0,1 dan n. Tetapi Anda harus memeriksa daftar null. Saya menggunakan ini di GWT, jadi saya baik-baik saja tanpa StringBuilder di sana. Dan untuk daftar pendek dengan hanya beberapa elemen tidak apa-apa juga di tempat lain;)

saya ambil
sumber
4

Jika seseorang tersandung ini di waktu yang lebih baru, saya telah menambahkan variasi sederhana menggunakan Java 8 reduce(). Ini juga mencakup beberapa solusi yang telah disebutkan oleh orang lain:

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
sumber
4

Di Android Anda harus menggunakan ini:

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

Saya pikir itu bukan ide yang baik mengkontruksi sql yang menggabungkan nilai klausa di mana seperti yang Anda lakukan:

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

Dimana valueXberasal dari daftar String.

Pertama, jika Anda membandingkan String, mereka harus dikutip, dan ini tidak sepele jika String dapat memiliki kutipan di dalamnya.

Kedua, jika nilai berasal dari pengguna, atau sistem lain, maka serangan injeksi SQL dimungkinkan.

Ini jauh lebih bertele-tele tetapi yang harus Anda lakukan adalah membuat String seperti ini:

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

dan kemudian mengikat variabel dengan Statement.setString(nParameter,parameterValue).

Telcontar
sumber
3

Hanya metode lain untuk mengatasi masalah ini. Bukan yang paling singkat, tetapi efisien dan menyelesaikan pekerjaan.

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

Ada beberapa pustaka Java pihak ketiga yang menyediakan metode penggabungan string, tetapi Anda mungkin tidak ingin mulai menggunakan pustaka hanya untuk sesuatu yang sederhana seperti itu. Saya hanya akan membuat metode pembantu seperti ini, yang menurut saya sedikit lebih baik daripada versi Anda, Ini menggunakan StringBuffer, yang akan lebih efisien jika Anda perlu menggabungkan banyak string, dan berfungsi pada koleksi jenis apa pun.

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

Saran lain dengan menggunakan Collection.toString () lebih pendek, tetapi itu bergantung pada Collection.toString () yang mengembalikan string dalam format yang sangat spesifik, yang secara pribadi tidak ingin saya andalkan.

Denis Fradlin
sumber
2

Jika Anda menggunakan Spring, Anda dapat melakukan:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(paket org.springframework.util)

Weekens
sumber
1

Saya tidak yakin seberapa "canggih" ini, tapi pasti sedikit lebih pendek. Ini akan bekerja dengan berbagai jenis koleksi, misalnya Set <Integer>, List <String>, dll.

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

Latihan untuk pembaca : ubah metode ini agar dapat menangani koleksi null / kosong dengan benar :)

Dónal
sumber
1

Yang membuat kode jelek adalah penanganan khusus untuk kasus pertama. Sebagian besar baris dalam cuplikan kecil ini ditujukan, bukan untuk melakukan tugas rutin kode, tetapi menangani kasus khusus tersebut. Dan itulah yang alternatif seperti pemecahan gimel, dengan memindahkan penanganan khusus di luar loop. Ada satu kasus khusus (yah, Anda dapat melihat awal dan akhir sebagai kasus khusus - tetapi hanya satu dari kasus tersebut yang perlu ditangani secara khusus), jadi penanganannya di dalam loop tidak perlu menjadi rumit.

Carl Manaster
sumber
1

Saya baru saja memeriksa tes untuk dolar perpustakaan saya :

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

itu membuat pembungkus yang lancar di sekitar daftar / array / string / dll hanya menggunakan satu impor statis :$ .

NB :

menggunakan rentang daftar sebelumnya dapat ditulis ulang sebagai $(1, 5).join(",")

dfa
sumber
1

Hal yang menyenangkan tentang ekspresi IN adalah jika Anda memiliki nilai berulang, itu tidak mengubah hasilnya. Jadi, cukup duplikat item pertama dan proses seluruh daftar. Ini mengasumsikan bahwa setidaknya ada satu item dalam daftar. Jika tidak ada item, saya sarankan untuk memeriksanya terlebih dahulu dan kemudian tidak menjalankan SQL sama sekali.

Ini akan melakukan trik, jelas dalam apa yang dilakukannya dan tidak bergantung pada pustaka eksternal apa pun:

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

Sementara saya pikir taruhan terbaik Anda adalah menggunakan Joiner dari Guava, jika saya mengkodekannya dengan tangan, saya menemukan pendekatan ini lebih elegan daripada bendera 'pertama' atau memotong koma terakhir.

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

jika Anda memiliki array yang dapat Anda lakukan:

Arrays.asList(parameters).toString()
cuaca berawan
sumber
1

Pilihan lain, berdasarkan apa yang saya lihat di sini (dengan sedikit modifikasi).

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

Bergabung 'metode' tersedia di Array dan kelas yang memperluas AbstractCollectionstetapi tidak mengganti toString()metode (seperti hampir semua koleksi di java.util).

Misalnya:

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

Itu cara yang cukup aneh karena hanya berfungsi untuk angka seperti data SQL.

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

StringUtils dari springframeowrk: spring-core

Sridhar
sumber
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 César
sumber
1
Ini adalah praktik yang buruk. Anda tidak dapat berasumsi bahwa implementasi toString berubah.
drindt
0
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" );  // replace [ or ] or blank

Representasi string terdiri dari daftar elemen koleksi dalam urutan yang dikembalikan oleh iteratornya, diapit oleh tanda kurung siku ("[]"). Elemen yang berdekatan dipisahkan oleh karakter "," (koma dan spasi).

Abstrak Koleksi javadoc

Todd Gatts
sumber
0

Token daftar = ArrayList baru (hasil); final StringBuilder builder = 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
sumber