Java'da bir dizi sınırlandırılmış öğe oluşturmanın en iyi yolu nedir?


317

Bir Java uygulamasında çalışırken, kaç tane öğenin önceden olacağını bilmeden başka bir web hizmetine geçmek için virgülle ayrılmış bir değerler listesi oluşturmam gerekiyordu. Başımın üstünden gelebileceğim en iyi şey şuydu:

public String appendWithDelimiter( String original, String addition, String delimiter ) {
    if ( original.equals( "" ) ) {
        return addition;
    } else {
        return original + delimiter + addition;
    }
}

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Her yerde oluşturulan dizeler olduğundan, bu özellikle verimli olmadığını, ancak optimizasyon daha netlik için gidiyordu farkındayım.

Ruby'de bunun yerine böyle daha zarif bir şey yapabilirim:

parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");

Ancak Java'nın birleştirme komutu olmadığından, eşdeğer bir şey bulamadım.

Peki, bunu Java ile yapmanın en iyi yolu nedir?


StringbBilder gitmenin yoludur - java.lang.StringBuilder.
Yusubov

Java 8 için şu cevaba bir göz atın: stackoverflow.com/a/22577623/1115554
micha

Yanıtlar:


542

Java Öncesi 8:

Apache'nin ortak dili burada arkadaşınızdır - Ruby'de bahsettiğinize çok benzer bir birleştirme yöntemi sağlar:

StringUtils.join(java.lang.Iterable,char)


Java 8:

Java 8, StringJoinerve aracılığıyla kutudan çıkmayı sağlar String.join(). Aşağıdaki snippet'ler bunları nasıl kullanabileceğinizi gösterir:

StringJoiner

StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"

String.join(CharSequence delimiter, CharSequence... elements))

String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"

String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"

2
Merak ediyorum - Koleksiyondaki bir Nesnenin Dize temsili sınırlayıcı karakterin kendisini içeriyorsa bu dikkate alıyor mu?
GreenieMeanie

4
Tam olarak ne aradığım: StringUtils.join (java.util.Collection, String) org.apache.commons.lang3.StringUtils paketinden, jar dosyası commons-lang3-3.0.1.jar
Umar

108
Android'de TextUtils.join () öğesini de kullanabilirsiniz.
James Wald

3
Bu en iyi yol değil. Koleksiyondaki Nesne boş / boş olsa bile karakteri her zaman ekler. Güzel ve temiz ama bazen çift sınırlayıcı karakter basacaktır.
Stephan Schielke

52

Java.util üzerinde çalışan küçük bir birleştirme tarzı yardımcı yöntem yazabilirsiniz.

public static String join(List<String> list, String delim) {

    StringBuilder sb = new StringBuilder();

    String loopDelim = "";

    for(String s : list) {

        sb.append(loopDelim);
        sb.append(s);            

        loopDelim = delim;
    }

    return sb.toString();
}

Sonra şöyle kullanın:

    List<String> list = new ArrayList<String>();

    if( condition )        list.add("elementName");
    if( anotherCondition ) list.add("anotherElementName");

    join(list, ",");

2
en az 2 uygulama (apache ve guava) zaten varsa neden kendi yönteminizi yazmalısınız?
Timofey

29
Bu, örneğin daha az dış bağımlılığa sahip olmak istiyorsa yararlı olabilir.
Thomas Zumbrunn

2
Ben şart yerine loopDelim nasıl kullanılır gibi. Bu bir tür hack'tir, ancak koşullu bir ifadeyi bir döngüden kaldırır. Yine de yineleyici kullanmayı ve döngüden önce ilk öğeyi eklemeyi tercih ediyorum, ama gerçekten güzel bir hack.
Vlasec

List<String>Başlatıcınızda Iterator<?>aynı etkiyi değiştirip değiştiremez misiniz ?
k_g

@ Zaman Eg apache boş dizeleri atlamaz.
banterCZ


31

Google'ın Guava kütüphanesi vardır com.google.common.base.Joiner bu görevleri çözmek için yardımcı olur sınıfını.

Örnekler:

"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog")); 
// returns "My pets are: rabbit, parrot, dog"

Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"

Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"

Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"

İşte Guava'nın dize yardımcı programları hakkında bir makale .


26

Java 8'de şunları kullanabilirsiniz String.join():

List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"

Ayrıca bir Stream API örneği için bu cevaba bir göz atın .


20

Genelleştirebilirsiniz, ancak söylediğiniz gibi Java'da bir birleşim yoktur.

Bu daha iyi olabilir.

public static String join(Iterable<? extends CharSequence> s, String delimiter) {
    Iterator<? extends CharSequence> iter = s.iterator();
    if (!iter.hasNext()) return "";
    StringBuilder buffer = new StringBuilder(iter.next());
    while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
    return buffer.toString();
}

Bu yanıta katılıyorum, ancak bir kişi imzayı AbstractCollection <String> yerine <String> Koleksiyonunu kabul edecek şekilde düzenleyebilir mi? Kodun geri kalanı aynı olmalıdır, ancak AbstractCollection burada önemli olmayan bir uygulama detayı olduğunu düşünüyorum.
Yasadışı Programcı

Daha Iterable<String>da iyisi, üzerinde iterasyon yapabileceğiniz gerçeğini kullanın ve kullanın. Örneğinizde, koleksiyondaki öğe sayısına ihtiyacınız yoktur, bu nedenle bu daha geneldir.
Jason Cohen

1
Ya da daha iyisi, Tekrarlanabilir <? Charsequence> öğesini genişlettikten sonra StringBuilders ve Strings ve streams ve diğer string benzeri şeylerin koleksiyonlarını kabul edebilirsiniz. :)
Jason Cohen

2
Bunun StringBuilderyerine kullanmak istiyorsunuz . Bunlar StringBuffer, gereksiz iplik güvenliği sağlamak dışında aynıdır . Birisi lütfen düzenleyebilir!
Casebash

1
Bu kodda birkaç hata var. 1. CharSequence'ın sermayesi s. 2. s.iterator () bir Yineleyici <? CharSequence> 'ı genişletir. 3. isEmpty()next()
Tekrarlanabilir

17

Java 8'de bunu şöyle yapabilirsiniz:

list.stream().map(Object::toString)
        .collect(Collectors.joining(delimiter));

listede boş değerler varsa:

list.stream().map(String::valueOf)
        .collect(Collectors.joining(delimiter))

ön ek ve son eki de destekler:

list.stream().map(String::valueOf)
        .collect(Collectors.joining(delimiter, prefix, suffix));

15

Dayalı bir yaklaşım kullanın java.lang.StringBuilder! ("Değiştirilebilir bir karakter dizisi.")

Bahsettiğiniz gibi, tüm bu dize birleştirmeleri her yerde Dizeler oluşturuyor. StringBuilderbunu yapmayacağım.

Neden StringBuilderyerine StringBuffer? Gönderen StringBuilderjavadoc:

Mümkün olduğunda, bu uygulamanın çoğu uygulamada daha hızlı olacağı için StringBuffer yerine kullanılması önerilir.


4
Evet. Ayrıca, StringBuffer iş parçacığı için güvenlidir, ancak StringBuilder değildir.
Jon Onstott

10

Google Koleksiyonlarını kullanırdım. Güzel bir Join tesis var.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Ama eğer kendi başıma yazmak istersem,

package util;

import java.util.ArrayList;
import java.util.Iterable;
import java.util.Collections;
import java.util.Iterator;

public class Utils {
    // accept a collection of objects, since all objects have toString()
    public static String join(String delimiter, Iterable<? extends Object> objs) {
        if (objs.isEmpty()) {
            return "";
        }
        Iterator<? extends Object> iter = objs.iterator();
        StringBuilder buffer = new StringBuilder();
        buffer.append(iter.next());
        while (iter.hasNext()) {
            buffer.append(delimiter).append(iter.next());
        }
        return buffer.toString();
    }

    // for convenience
    public static String join(String delimiter, Object... objs) {
        ArrayList<Object> list = new ArrayList<Object>();
        Collections.addAll(list, objs);
        return join(delimiter, list);
    }
}

Bir nesne koleksiyonu ile daha iyi çalıştığını düşünüyorum, çünkü şimdi onlara katılmadan önce nesnelerinizi dizelere dönüştürmek zorunda değilsiniz.


8

Apache commons StringUtils sınıfında bir join yöntemi vardır.



3

Bunun için Java'nın StringBuildertürünü kullanabilirsiniz . Ayrıca var StringBuffer, ancak genellikle gereksiz olan ekstra iş parçacığı güvenlik mantığı içeriyor.


3

Ve asgari bir tane (Apache Commons veya Gauva'yı sadece dizelere katılmak uğruna proje bağımlılıklarına dahil etmek istemiyorsanız)

/**
 *
 * @param delim : String that should be kept in between the parts
 * @param parts : parts that needs to be joined
 * @return  a String that's formed by joining the parts
 */
private static final String join(String delim, String... parts) {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < parts.length - 1; i++) {
        builder.append(parts[i]).append(delim);
    }
    if(parts.length > 0){
        builder.append(parts[parts.length - 1]);
    }
    return builder.toString();
}

File.separator yerine delim kullanın.
Pradeep Kumar

3

StringBuilder ve sınıfı kullanma Separator

StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
    buf.append(sep).append(each);
}

Ayırıcı bir sınırlayıcı sarar. toStringBoş dize döndüren ilk çağrıda ayırıcı olmadığı sürece sınırlayıcı Ayırıcı yöntemiyle döndürülür !

Sınıf için kaynak kodu Separator

public class Separator {

    private boolean skipFirst;
    private final String value;

    public Separator() {
        this(", ");
    }

    public Separator(String value) {
        this.value = value;
        this.skipFirst = true;
    }

    public void reset() {
        skipFirst = true;
    }

    public String toString() {
        String sep = skipFirst ? "" : value;
        skipFirst = false;
        return sep;
    }

}

Birleştirme yerine Separatorkullanmak için değiştirmeye ne dersiniz ? StringBuilderStrings
Mohamed Nuur

@Mohamed Separatorsadece sınırlayıcıyı döndürür, dizenin kendisini birleştirmez.
akuhn

Bu sınıf nedir Separator??? Neden sadece basit bir String kullanmıyorsunuz ..?!
maxxyme

2

Neden kendi join () yönteminizi yazmıyorsunuz? Dizeler ve bir sınırlayıcı Dize parametre koleksiyonu olarak alacaktır. Yöntem içinde koleksiyon üzerinde yineleme ve sonuç bir StringBuffer oluşturun.


2

Spring MVC kullanıyorsanız, aşağıdaki adımları deneyebilirsiniz.

import org.springframework.util.StringUtils;

List<String> groupIds = new List<String>;   
groupIds.add("a");    
groupIds.add("b");    
groupIds.add("c");

String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());

Sonuçlanacak a,b,c


2

Eclipse Collections kullanıyorsanız , makeString()veya kullanabilirsiniz appendString().

makeString()Stringbenzeri bir gösterim döndürür toString().

Üç şekli vardır

  • makeString(start, separator, end)
  • makeString(separator) varsayılanlar boş dizelerle başlar ve biter
  • makeString()ayırıcıyı varsayılan olarak ", "(virgül ve boşluk)

Kod örneği:

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));

appendString()benzer makeString(), ancak Appendable(gibi StringBuilder) ve ekler void. Ek üç argümanı olan Uzatılabilir ile aynı üç forma sahiptir.

MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());

Koleksiyonunuzu Eclipse Collections türüne dönüştüremiyorsanız, ilgili adaptörle uyarlamanız yeterlidir.

List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");

Not: Eclipse koleksiyonları için bir komisyoncuyum.


2

Java 8 Yerel Türü

List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));

Java 8 Özel Nesnesi:

List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));

1

Muhtemelen kullanmalısınız StringBuilderile appendsonucunuzu inşa etmek yöntemle ancak Java teklifine olduğu gibi aksi takdirde bu çözüm iyiliği gibidir.


1

Neden Java'da ruby'de yaptığınızla aynı şeyleri yapmıyorsunuz, bu sadece diziye tüm parçaları ekledikten sonra ayırıcı ayrılmış dizeyi yaratıyor?

ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
    b.append(sep);
    b.append(p);
    sep = "yourDelimiter";
}

Bunu döngü için ayrı bir yardımcı yöntemde taşımak ve ayrıca StringBuffer yerine StringBuilder kullanmak isteyebilirsiniz ...

Düzenleme : Eklerin sırası düzeltildi.


Evet, neden StringBuilder yerine StringBuffer kullanıyorsunuz (Java 1.5+ kullanıyorsanız)? Ayrıca yanlış bir şekilde ekler var.
Tom Hawtin - çakmak hattı

1

Java 5 değişken argümanları sayesinde, tüm dizelerinizi açıkça bir koleksiyona veya diziye doldurmanız gerekmez:

import junit.framework.Assert;
import org.junit.Test;

public class StringUtil
{
    public static String join(String delim, String... strings)
    {
        StringBuilder builder = new StringBuilder();

        if (strings != null)
        {
            for (String str : strings)
            {
                if (builder.length() > 0)
                {
                    builder.append(delim).append(" ");
                }
                builder.append(str);
            }
        }           
        return builder.toString();
    }
    @Test
    public void joinTest()
    {
        Assert.assertEquals("", StringUtil.join(",", null));
        Assert.assertEquals("", StringUtil.join(",", ""));
        Assert.assertEquals("", StringUtil.join(",", new String[0]));
        Assert.assertEquals("test", StringUtil.join(",", "test"));
        Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
        Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
    }
}

1

Bahar bağlamında olanlar için StringUtils sınıfı da yararlıdır:

Gibi birçok yararlı kısayol vardır:

  • collectionToCommaDelimitedString (Koleksiyon koleksiyonu)
  • collectionToDelimitedString (Koleksiyon coll, Dize sınırlaması)
  • arrayToDelimitedString (Object [] arr, String delim)

Ve bircok digerleri.

Zaten Java 8 kullanmıyorsanız ve zaten bir Spring bağlamındaysanız bu yardımcı olabilir.

Bunu daha kolay olan Koleksiyon desteği için Apache Commons'a karşı tercih ederim (çok iyi olmasına rağmen):

// Encoding Set<String> to String delimited 
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");

// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);

0

Böyle bir şey deneyebilirsiniz:

StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();

Bu, dizenin sonunda kaçınmayı umduğum bir başıboş virgül bırakacak gibi görünüyor. (Elbette, orada olduğunu bildiğiniz için, onu kesebilirsiniz, ancak bu da biraz
yetersiz

0

Temel olarak böyle bir şey:

public static String appendWithDelimiter(String original, String addition, String delimiter) {

if (original.equals("")) {
    return addition;
} else {
    StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
        sb.append(original);
        sb.append(delimiter);
        sb.append(addition);
        return sb.toString();
    }
}

Burada sorun o appendWithDelimiter () allot çağırıyor gibi görünüyor. Çözüm, yalnızca bir StringBuffer örneği ve bu tek örnekle çalışmalıdır.
Stu Thompson

0

Bu gerçekten daha iyi olup olmadığını bilmiyorum, ama en azından biraz daha verimli olabilir StringBuilder kullanıyor.

Herhangi bir parametre sınırlaması yapmadan ÖNCE parametre listesini oluşturabiliyorsanız, aşağıda daha genel bir yaklaşım sunulmaktadır.

// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
    StringBuilder sb = new StringBuilder(original);
    if(sb.length()!=0) {
        sb.append(delimiter).append(addition);
    } else {
        sb.append(addition);
    }
    return sb.toString();
}


// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
    StringBuilder sb = new StringBuilder();
    for (String string : strings) {
        if(sb.length()!=0) {
            sb.append(delimiter).append(string);
        } else {
            sb.append(string);
        }
    }

    return sb.toString();
}

public void testAppendWithDelimiters() {
    String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}

0

Yaklaşımınız çok kötü değil, ancak + işaretini kullanmak yerine bir StringBuffer kullanmalısınız. +, Her bir işlem için yeni bir String örneği oluşturulmasının büyük dezavantajına sahiptir. İpiniz ne kadar uzun olursa, ek yük o kadar büyük olur. Yani bir StringBuffer kullanmak en hızlı yol olmalıdır:

public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
        if ( original == null ) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(addition);
                return buffer;
        } else {
                buffer.append(delimiter);
                buffer.append(addition);
                return original;
        }
}

Dizenizi oluşturmayı bitirdikten sonra, döndürülen StringBuffer'da toString () öğesini çağırmanız yeterlidir.


1
StringBuffer'ı burada kullanmak, iyi bir nedenden ötürü daha yavaş hale getirecektir. + İşlecini kullanmak dahili olarak daha hızlı StringBuilder'ı kullanır, böylece bunun yerine StringBuffer'ı kullanarak gereksinim duymadığı iş parçacığı güvenliğinden başka bir şey kazanmaz.
Fredrik

Ayrıca ... "+ işareti, her bir işlem için yeni bir String örneğinin oluşturulmasının büyük dezavantajına sahiptir" yanlıştır. Derleyici, onlardan StringBuilder.append () çağrıları oluşturur.
Fredrik

0

Dize birleştirme kullanmak yerine, kodunuz işlenmemişse StringBuilder'ı ve varsa StringBuffer'ı kullanmalısınız.


0

Bunu olması gerekenden biraz daha karmaşık hale getiriyorsunuz. Örneğinizin sonundan başlayalım:

String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );

Bir String yerine bir StringBuilder kullanmanın küçük bir değişikliği ile, bu olur:

StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...

İşiniz bittiğinde (birkaç koşulu da kontrol etmeniz gerektiğini varsayalım), kuyruk virgülünü aşağıdaki gibi bir komutla kaldırdığınızdan emin olun:

if (parameterString.length() > 0) 
    parameterString.deleteCharAt(parameterString.length() - 1);

Ve son olarak, istediğiniz dizeyi alın

parameterString.toString();

Eklemek için ikinci çağrıdaki "," ifadesini, herhangi bir şeye ayarlanabilen genel bir sınırlayıcı dizeyle değiştirebilirsiniz. Eklemeniz gerektiğini bildiğiniz şeylerin bir listesine sahipseniz (koşulsuz olarak), bu kodu dizelerin listesini alan bir yönteme koyabilirsiniz.


0
//Note: if you have access to Java5+, 
//use StringBuilder in preference to StringBuffer.  
//All that has to be replaced is the class name.  
//StringBuffer will work in Java 1.4, though.

appendWithDelimiter( StringBuffer buffer, String addition, 
    String delimiter ) {
    if ( buffer.length() == 0) {
        buffer.append(addition);
    } else {
        buffer.append(delimiter);
        buffer.append(addition);
    }
}


StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) { 
    appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
    appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}

//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString(); 

0

Yani, aradığınız gibi göründüğünü hissetmek için yapabileceğiniz birkaç şey:

1) List sınıfını genişletin ve birleştirme yöntemini ekleyin. Join yöntemi, sınırlayıcıyı birleştirme ve ekleme işini yapacaktır (join yöntemine bir param olabilir)

2) Java 7'nin java'ya uzantı yöntemleri ekleyeceği anlaşılıyor - bu sadece bir sınıfa belirli bir yöntem eklemenizi sağlar: böylece bu birleştirme yöntemini yazabilir ve List'e veya hatta bir uzantı yöntemi olarak ekleyebilirsiniz. Toplamak.

Çözüm 1 muhtemelen tek gerçekçi olanıdır, ancak Java 7 henüz dışarıda olmadığından :) Ama gayet iyi çalışmalıdır.

Bunların her ikisini de kullanmak için, tüm öğelerinizi her zamanki gibi Liste veya Koleksiyona ekler ve ardından yeni 'özel yöntem' olarak adlandırılır.

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.