Bir Koleksiyon / Dizi / Listeden virgülle ayrılmış Dizeler oluşturmanın en karmaşık yolu?


98

Veritabanları ile çalışmam sırasında sorgu dizeleri yazdığımı fark ettim ve bu dizelerde bir listeden / diziden / koleksiyondan where-cümlesine birkaç kısıtlama koymam gerekiyor. Şöyle görünmeli:

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

Bunu, dizelerden oluşan bir koleksiyonunuz olduğu ve bu dizelerin virgülle ayrılmış bir listesini yalnızca bir dizede oluşturmak istediğiniz sorusuna indirgeyerek basitleştirebilirsiniz.

Şimdiye kadar kullandığım yaklaşımım şöyle bir şey:

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

Ama gördüğünüz gibi bu çok çirkin. İlk bakışta orada ne olduğunu göremezsiniz, özellikle inşa edilen dizeler (her SQL sorgusu gibi) karmaşıklaştığında.

(Daha) zarif yolunuz nedir?


Muhtemelen yukarıda gösterilen SQL aslında şu şekilde görünmelidir: customer.id içinde (34, 26, 2) 'de müşteri olan * öğesini seçin;
Dónal

Liste öğelerinin (dizelerin) kendilerinin virgül veya çift tırnak içerdiğinde ve tırnak işaretleri ile kaçmaları gerektiğinde, zor bir kısım vardır. Hiçbir şeyi gözden kaçırmadıysam, yukarıdaki örnekler bunu dikkate almıyor ve tüm metinlerde dönüp virgül aramaktan nefret ediyorum. Bunu çözmenin daha iyi bir yolu var mı?
Samuray Kız



Yanıtlar:


85

Not: Bu yanıt, 11 yıl önce yazıldığı zaman iyiydi, ancak şimdi hem yalnızca yerleşik Java sınıflarını kullanarak hem de bir yardımcı program kitaplığı kullanarak bunu tek bir satırda daha temiz bir şekilde yapmak için çok daha iyi seçenekler var. Aşağıdaki diğer cevaplara bakın.


Dizeler değişmez olduğundan, koddaki String'i değiştirecekseniz StringBuilder sınıfını kullanmak isteyebilirsiniz.

StringBuilder sınıfı, içeriği değiştirildiğinde daha fazla bellek ayıran değiştirilebilir bir String nesnesi olarak görülebilir.

Sorudaki orijinal öneri, sondaki gereksiz virgülle ilgilenilerek daha net ve verimli bir şekilde yazılabilir :

    StringBuilder result = new StringBuilder();
    for(String string : collectionOfStrings) {
        result.append(string);
        result.append(",");
    }
    return result.length() > 0 ? result.substring(0, result.length() - 1): "";

7
Bunun koleksiyonunuzun en az bir öğeye sahip olmasını gerektirdiğini unutmayın.
Guus


Boş bir liste için önerilen düzeltmeye bakın.
gimel

1
guava cevabı daha iyi. tekerleği yeniden icat etmeye gerek yok.
davidjnelson

1
@ xtreme-biker Oldukça modern bir derleyici ile StringBuilder otomatik olarak kullanılabilir. + = Kullanmadan önce ortamınızı kontrol edin. Stackoverflow.com/questions/1532461/… sayfasına
gimel


76

Bugün bunu yapan koda baktım. Bu, AviewAnew'in cevabının bir varyasyonudur.

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

Kullandığımız StringUtils (<- commons.lang 2.x veya commons.lang 3.x bağlantısı ) Apache Commons'tandır .


... ve StringUtils nereden geliyor?
vwegert

1
Ah, iyi nokta. Bu koda bakmayalı bir süre oldu, ancak org.apache.commons.lang.StringUtils kullandığımıza inanıyorum.
Ogre Mezmur


2
Güzel, teşekkürler. StringUtils # join aynı zamanda bir Yinelenebilir üzerinde de çalışır, bu nedenle muhtemelen koleksiyonunuzu önce bir diziye dönüştürmenize gerek yoktur.
Roy

47

Bu döngüyü yazma şeklim:

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

Eylül'ün performansı konusunda endişelenme. Bir görev çok hızlıdır. Hotspot, bir döngünün ilk yinelemesini yine de soyma eğilimindedir (çünkü genellikle boş ve mono / bimorfik satır içi kontroller gibi tuhaflıklarla uğraşmak zorundadır).

Çok kullanırsanız (birden fazla), paylaşılan bir yönteme koyun.

Stackoverflow'da bir kimlik listesinin bir SQL ifadesine nasıl ekleneceği ile ilgili başka bir soru daha var.


42

Java 8'den beri şunları kullanabilirsiniz:


3
Bu iyi! ToString () tarafından kapsanmayan özel bir dize dönüşümü gerektiren nesneleri işliyorsanız, Object :: toString öğesini, sınıfınızı bir String ile eşleyen bir java.util.function.Function <YourType, String> ile değiştirin.
Torben

2
Ayrıca, bunu şu şekilde kullanabilirsiniz: cats.stream().map(cat -> cat.getName()).collect(Collectors.joining(","));koleksiyonunuzdaki tek bir değişken için.
numsu

Bunun performansını merak ediyorum stream. İnt [] veya long [] ya da değerin basitçe dönüştürülebileceği diğer diziler için String, akışsız bir çözüm arardım. Aslında bakıyorum.
Adam

11

Yineleyici deyimini zarif buldum, çünkü daha fazla öğe için bir testi var (kısalık için ihmal edilmiş null / boş testi):

public static String convert(List<String> list) {
    String res = "";
    for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
        res += iterator.next() + (iterator.hasNext() ? "," : "");
    }
    return res;
}

... ve muhtemelen 'hasNext ()' çağrısının ne kadar karmaşık olduğuna bağlı olarak kabul edilen çözümden daha az etkilidir. Ayrıca, muhtemelen String birleştirme yerine bir StringBuilder kullanıyor olmalısınız.
Stephen C

Tamam, verimlilik konusunda seçici olmak istiyorsanız, bir StringWriter kullanın;)
Miguel Ping

8

Bunun birçok manuel çözümü var, ancak Julie'nin yukarıdaki yanıtını tekrarlamak ve güncellemek istedim. Google collections Marangoz sınıfını kullanın .

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

Değişken args, iterables ve dizileri işler ve birden fazla karakterin ayırıcılarını düzgün bir şekilde işler (gimmel'in cevabının aksine). Ayrıca, ihtiyacınız olursa listenizdeki boş değerleri de işler.


7

İşte önceki önerilerin bir kombinasyonundan oluşturduğum inanılmaz genel bir sürüm:

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'de mevcuttur.

alternatifi (google guava bağımlılığı eklemeye gerek kalmadan):

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

5

Deneyebilirsin

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

4

Bu, Guava veya Apache Commons kullanımı dışında şimdiye kadarki en kısa çözüm olacak

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

0,1 ve n eleman listesiyle iyi. Ancak boş listeyi kontrol etmeniz gerekecek. Bunu GWT'de kullanıyorum, bu yüzden orada StringBuilder olmadan iyiyim. Ve sadece birkaç öğeli kısa listeler için başka yerlerde de sorun yok;)


4

Yakın zamanda birisi bununla karşılaşmışsa, Java 8 kullanarak basit bir varyasyon ekledim reduce(). Aynı zamanda diğerleri tarafından belirtilen çözümlerden bazılarını da içerir:

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

Android'de şunu kullanmalısınız:

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

4

Sanırım, yaptığınız gibi where cümleci değerlerini birleştiren sql'yi oluşturmak iyi bir fikir değil:

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

valueXDizeler listesinden nereden geliyor.

İlk olarak, Dizeleri karşılaştırıyorsanız, bunların alıntılanması gerekir ve Dizelerin içinde bir alıntı olabilirse, bu önemsiz değildir.

İkinci olarak, değerler kullanıcıdan veya başka bir sistemden geliyorsa, bir SQL enjeksiyon saldırısı mümkündür.

Bu çok daha ayrıntılı ama yapmanız gereken şuna benzer bir String oluşturmaktır:

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

ve sonra değişkenleri ile bağlayın Statement.setString(nParameter,parameterValue).


3

Bu sorunu çözmek için başka bir yöntem. En kısa değil, ama verimli ve işi hallediyor.

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

Dizge birleştirme yöntemi sağlayan bazı üçüncü taraf Java kitaplıkları vardır, ancak muhtemelen böyle basit bir şey için kitaplık kullanmaya başlamak istemezsiniz. Ben sadece bunun gibi bir yardımcı yöntem yaratırdım, sizin sürümünüzden biraz daha iyi olduğunu düşünüyorum, StringBuffer kullanıyor, eğer birçok dizgiye katılmanız gerekirse daha verimli olacak ve her türden bir koleksiyon üzerinde çalışıyor.

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 () kullanımıyla ilgili başka bir öneri daha kısadır, ancak bu, Collection.toString () 'nin çok özel bir formatta bir dizeyi döndürmesine dayanır, ki bu kişisel olarak güvenmek istemiyorum.


2

Spring kullanıyorsanız şunları yapabilirsiniz:

StringUtils.arrayToCommaDelimitedString(
    collectionOfStrings.toArray()
)

(paket org.springframework.util)


1

Bunun ne kadar "karmaşık" olduğundan emin değilim, ama kesinlikle biraz daha kısa. Çeşitli farklı koleksiyon türleriyle çalışacaktır, örneğin Set <Integer>, List <String> vb.

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

Okuyucu için alıştırma : bu yöntemi, boş / boş bir koleksiyonu doğru şekilde işleyecek şekilde değiştirin :)


1

Kodu çirkin kılan, ilk durum için özel işlemdir. Bu küçük parçadaki satırların çoğu, kodun rutin işini yapmaya değil, bu özel durumu ele almaya ayrılmıştır. Ve bu, özel işlemeyi döngünün dışına taşıyarak, gimel'in gibi alternatiflerin çözdüğü şeydir. Özel bir durum vardır (hem başlangıcı hem de sonu özel durumlar olarak görebilirsiniz - ancak bunlardan yalnızca birinin özel olarak ele alınması gerekir), bu nedenle onu döngü içinde ele almak gereksiz yere karmaşıktır.


1

Kütüphane dolarım için bir test yaptım :

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

o kullanarak listeleri / diziler / dizeleri / vb etrafında akıcı sarıcı oluşturmak tek statik ithalat : $.

NB :

aralıkları kullanarak önceki liste şu şekilde yeniden yazılabilir: $(1, 5).join(",")


1

IN ifadesinin güzel yanı, tekrar eden değerlere sahipseniz, sonucu değiştirmemesidir. Yani, sadece ilk öğeyi kopyalayın ve tüm listeyi işleyin. Bu, listede en az bir öğe olduğunu varsayar. Hiç öğe yoksa, önce bunu kontrol etmenizi ve sonra SQL'i hiç çalıştırmamayı öneririm.

Bu, hile yapacak, ne yaptığına dair açıktır ve herhangi bir harici kütüphaneye bağlı değildir:

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

1

En iyi bahsinizin Guava'dan Joiner'ı kullanmak olduğunu düşünsem de, el ile kodlarsam, bu yaklaşımı 'ilk' bayraktan veya son virgülü kesmekten daha zarif buluyorum.

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

bir diziniz varsa şunları yapabilirsiniz:

Arrays.asList(parameters).toString()

1

Burada gördüklerime dayanan başka bir seçenek (küçük değişikliklerle).

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

Birleştirme 'yöntemleri', Dizilerde ve yöntemi genişleten AbstractCollectionsancak geçersiz kılmayan sınıflarda mevcuttur toString()(neredeyse tüm koleksiyonlar gibi java.util).

Örneğin:

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

Bu oldukça tuhaf bir yoldur çünkü yalnızca veri SQL açısından benzer sayılar için çalışır.


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

Springframeowrk'tan StringUtils: yay-çekirdek



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
Bu kötü bir uygulamadır. ToString uygulamasının değişeceği varsayımında bulunamazsınız.
drindt

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

Dize temsili, koleksiyon öğelerinin yineleyicisi tarafından döndürüldükleri sırayla köşeli parantezler ("[]") içine alınmış bir listesinden oluşur. Bitişik öğeler "," (virgül ve boşluk) karakterleriyle ayrılır.

AbstractCollection javadoc


0

Liste simgesi = new ArrayList (sonuç); final StringBuilder oluşturucu = 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 ();

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.