Каков наилучший способ создания строки разделенных элементов в 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?

Ответ 1

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

Ответ 2

Вы можете написать небольшой метод утилиты join-style, который работает на 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, ",");

Ответ 4

Google Guava library имеет 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.

Ответ 5

В 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.

Ответ 6

Вы можете обобщить его, но нет никакого соединения в 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();
}

Ответ 7

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

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

Почему StringBuilder вместо StringBuffer? Из StringBuilder javadoc:

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

Ответ 8

Я бы использовал коллекцию Google. Существует хороший объект Join.
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);
    }
}

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

Ответ 9

в Java 8 вы можете сделать это как:

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

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

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

Ответ 11

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

Ответ 12

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

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

Сепаратор обертывает разделитель. Разделитель возвращается методом Separator 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;
    }

}

Ответ 13

Java 8

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

Ответ 14

Если вы используете 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

Ответ 15

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

Ответ 16

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

Ответ 17

Почему вы не делаете в 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...

Изменить: исправлен порядок добавлений.

Ответ 18

С переменными args 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"));
    }
}

Ответ 19

Если вы используете 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. Он имеет те же три формы, с дополнительным первым аргументом, Appendable.

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

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

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

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

Ответ 20

И минимальный (если вы не хотите включать 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 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();
}

Ответ 21

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

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

  • collectionToCommaDelimitedString (коллекция coll)
  • collectionToDelimitedString (Collection coll, String delim)
  • 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);

Ответ 22

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

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

Ответ 23

Итак, в основном что-то вроде этого:

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

Ответ 24

Не знаю, действительно ли это лучше, но, по крайней мере, с помощью 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");
}

Ответ 25

Ваш подход не так уж плох, но вы должны использовать 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.

Ответ 26

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

Ответ 27

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

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

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

Ответ 28

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

Ответ 29

Итак, несколько вещей, которые вы можете сделать, чтобы почувствовать, что кажется, что вы ищете:

1) Расширьте класс List и добавьте к нему метод join. Метод join просто выполнит работу по конкатенации и добавлению разделителя (который может быть параметром для метода соединения)

2) Похоже, что Java 7 добавляет методы расширения в java, что позволяет просто присоединить к классу определенный метод: так что вы можете написать этот метод соединения и добавить его как метод расширения в список или даже в коллекцию.

Решение 1, вероятно, является единственным реалистичным, хотя, поскольку Java 7 еще не завершен:) Но он должен работать нормально.

Чтобы использовать оба эти параметра, вы просто добавляете все свои элементы в список или коллекцию, как обычно, а затем вызываете новый настраиваемый метод, чтобы "присоединиться" к ним.

Ответ 30

с помощью Dollar прост, как печатать:

String joined = $(aCollection).join(",");

NB: он работает также для массивов и других типов данных

Реализация

Внутри он использует очень аккуратный трюк:

@Override
public String join(String separator) {
    Separator sep = new Separator(separator);
    StringBuilder sb = new StringBuilder();

    for (T item : iterable) {
        sb.append(sep).append(item);
    }

    return sb.toString();
}

класс Separator возвращает пустой String только в первый раз, когда он вызывается, затем возвращает разделитель:

class Separator {

    private final String separator;
    private boolean wasCalled;

    public Separator(String separator) {
        this.separator = separator;
        this.wasCalled = false;
    }

    @Override
    public String toString() {
        if (!wasCalled) {
            wasCalled = true;
            return "";
        } else {
            return separator;
        }
    }
}