Каков наилучший способ построить строку элементов с разделителями в Java?

Работая в приложении Java, я недавно должен был собрать список значений, разделенных запятыми, для передачи в другой веб-сервис, не зная, сколько элементов будет заранее. Лучшее, что я мог придумать, это было примерно так:

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

Я понимаю, что это не особенно эффективно, поскольку повсюду создаются строки, но я собирался сделать это для большей ясности, чем для оптимизации.

В Ruby я могу сделать что-то вроде этого, что выглядит намного элегантнее:

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

Но поскольку в Java нет команды соединения, я не смог найти ничего эквивалентного.

Итак, каков наилучший способ сделать это на Java?

264

35 ответов

Решение

До Java 8:

Apache commons lang - ваш друг здесь - он предоставляет метод соединения, очень похожий на тот, на который вы ссылаетесь в Ruby:

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


Java 8:

Java 8 обеспечивает соединение из коробки через StringJoiner а также String.join(), Фрагменты ниже показывают, как вы можете их использовать:

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"
472

Вы можете написать небольшой метод-метод в стиле соединения, который работает с java.util.Lists.

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

Тогда используйте это так:

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

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

    join(list, ",");
50

В случае Android класс StringUtils из общего достояния недоступен, поэтому для этого я использовал

android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)

http://developer.android.com/reference/android/text/TextUtils.html

43

В библиотеке Google Guava есть класс com.google.common.base.Joiner, который помогает решать такие задачи.

Образцы:

"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"

Вот статья о строковых утилитах Guava.

31

В Java 8 вы можете использовать String.join():

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

Также взгляните на этот ответ для примера Stream API.

24

Вы можете обобщить это, но в Java нет объединения, как вы хорошо сказали.

Это может работать лучше.

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

Используйте подход, основанный на java.lang.StringBuilder! ("Изменчивая последовательность символов. ")

Как вы упомянули, все эти объединения строк создают строки повсюду. StringBuilder не буду этого делать

Зачем StringBuilder вместо StringBuffer? От StringBuilder Javadoc:

Там, где это возможно, рекомендуется использовать этот класс вместо StringBuffer, так как он будет быстрее в большинстве реализаций.

15

В Java 8 вы можете сделать это следующим образом:

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

если в списке есть нули, вы можете использовать:

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

Я бы использовал Google Collections. Есть хорошая возможность присоединиться.
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html

Но если бы я хотел написать это сам,

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

Я думаю, что это лучше работает с коллекцией объектов, так как теперь вам не нужно конвертировать ваши объекты в строки, прежде чем присоединиться к ним.

10

Apache commons StringUtils класс имеет метод соединения.

8

Java 8

stringCollection.stream().collect(Collectors.joining(", "));
4

Вы можете использовать Java StringBuilder типа для этого. Есть также StringBuffer, но он содержит дополнительную логику безопасности потока, которая часто не нужна.

3

Используйте StringBuilder и класс Separator

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

Разделитель переносит разделитель. Разделитель возвращается разделителем toString метод, если только при первом вызове, который возвращает пустую строку!

Исходный код для класса 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;
    }

}
3

И минимальный (если вы не хотите включать Apache Commons или Gauva в зависимости проекта только для объединения строк)

/**
 *
 * @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();
}
2

Если вы используете Spring MVC, вы можете попробовать следующие шаги.

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

Это приведет к a,b,c

2

Если вы используете Eclipse Collections, вы можете использовать makeString() или же appendString(),

makeString() возвращает String представление, подобное toString(),

Имеет три формы

  • makeString(start, separator, end)
  • makeString(separator) по умолчанию начало и конец пустые строки
  • makeString() по умолчанию разделитель ", " (запятая и пробел)

Пример кода:

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() похож на makeString(), но это добавляет к Appendable (лайк StringBuilder) и является void, Он имеет те же три формы, с дополнительным первым аргументом, "Добавляемый".

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

Если вы не можете преобразовать свою коллекцию в тип коллекций Eclipse, просто адаптируйте ее с помощью соответствующего адаптера.

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

Примечание: я являюсь коммиттером для коллекций Eclipse.

2

Почему бы не написать свой собственный метод join()? В качестве параметров он будет принимать коллекцию Strings и разделитель String. Внутри метода переберите коллекцию и создайте свой результат в StringBuffer.

2

Вы, вероятно, должны использовать StringBuilder с append метод для построения вашего результата, но в остальном это такое же хорошее решение, как и Java.

1

Для тех, кто находится в контексте Spring, их класс StringUtils также полезен:

Есть много полезных ярлыков, таких как:

  • collectionToCommaDelimitedString (Collection coll)
  • collectionToDelimitedString (Коллекция coll, разделитель строк)
  • arrayToDelimitedString (Object [] arr, String delim)

и много других.

Это может быть полезно, если вы еще не используете Java 8 и уже находитесь в контексте Spring.

Я предпочитаю это против Apache Commons (хотя и очень хорошо) для поддержки Коллекции, которая проще как это:

// 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);
1

Почему бы вам не сделать в Java то же самое, что вы делаете в ruby, то есть создать разделенную разделителем строку только после того, как вы добавили все части в массив?

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

Вы можете переместить цикл for в отдельный вспомогательный метод, а также использовать StringBuilder вместо StringBuffer...

Редактировать: исправлен порядок добавления.

1

Благодаря переменным аргументам Java 5 вам не нужно явно вставлять все строки в коллекцию или массив:

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

В общем, как-то так:

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

Лично я довольно часто использую следующее простое решение для целей регистрации:

List lst = Arrays.asList("ab", "bc", "cd");
String str = lst.toString().replaceAll("[\\[\\]]", "");
0

Вы можете попробовать что-то вроде этого:

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

Родной тип Java 8

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

Java 8 Custom Object:

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

Вы делаете это немного сложнее, чем должно быть. Давайте начнем с конца вашего примера:

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

С небольшим изменением использования StringBuilder вместо String это становится:

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

Когда вы закончите (я предполагаю, что вы должны проверить еще несколько условий), просто убедитесь, что вы удаляете запятую с помощью такой команды:

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

И наконец, получите строку, которую вы хотите с

parameterString.toString();

Вы также можете заменить "," во втором вызове, чтобы добавить общую строку разделителя, которая может быть установлена ​​на что угодно. Если у вас есть список вещей, которые, как вы знаете, нужно добавить (безоговорочно), вы можете поместить этот код в метод, который принимает список строк.

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

Не знаю, действительно ли это лучше, но, по крайней мере, он использует StringBuilder, который может быть немного более эффективным.

Ниже приведен более общий подход, если вы можете создать список параметров ДО того, как выполните какой-либо разделитель параметров.

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

Вместо использования конкатенации строк вы должны использовать StringBuilder, если ваш код не является многопоточным, и StringBuffer, если он есть.

0

Ваш подход не так уж плох, но вы должны использовать StringBuffer вместо знака +. + Имеет большой недостаток в том, что для каждой отдельной операции создается новый экземпляр String. Чем длиннее ваша строка, тем больше накладные расходы. Поэтому использование StringBuffer должно быть самым быстрым способом:

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

После того, как вы закончили создание вашей строки, просто вызовите toString() для возвращенного StringBuffer.

0
Другие вопросы по тегам