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

<p>работая в приложении java, я недавно должен был собрать список значений, разделенных запятыми, для передачи в другой веб-сервис, не зная, сколько элементов будет заранее. лучшее, что я мог придумать, это было примерно так:</p><pre><code>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", "," ); </code></pre><p>я понимаю, что это не особенно эффективно, поскольку повсюду создаются строки, но я собирался сделать это для большей ясности, чем для оптимизации.</p><p>в ruby я могу сделать что-то вроде этого, что выглядит намного элегантнее:</p><pre><code>parameterarray = []; parameterarray << "elementname" if condition; parameterarray << "anotherelementname" if anothercondition; parameterstring = parameterarray.join(","); </code></pre><p>но поскольку в java отсутствует команда соединения, я не смог найти ничего эквивалентного.</p><p>итак, каков наилучший способ сделать это на java?</p>

<h3>pre java 8:</h3><p>apache commons lang - ваш друг здесь - он предоставляет метод соединения, очень похожий на тот, который вы используете в ruby:</p><p> <a href="http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/stringutils.html#join(java.lang.iterable,%20char)"><code>stringutils.join(java.lang.iterable,char)</code></a> </p><hr><h3>java 8:</h3><p>java 8 обеспечивает соединение из коробки через <code>stringjoiner</code> и <code>string.join()</code>. фрагменты ниже показывают, как вы можете их использовать:</p><p> <a href="https://docs.oracle.com/javase/8/docs/api/java/util/stringjoiner.html"><code>stringjoiner</code></a> </p><pre><code>stringjoiner joiner = new stringjoiner(","); joiner.add("01").add("02").add("03"); string joinedstring = joiner.tostring(); // "01,02,03" </code></pre><hr><p> <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/string.html#join-java.lang.charsequence-java.lang.charsequence...-"><code>string.join(charsequence delimiter, charsequence... elements))</code></a> </p><pre><code>string joinedstring = string.join(" - ", "04", "05", "06"); // "04 - 05 - 06" </code></pre><hr><p> <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/string.html#join-java.lang.charsequence-java.lang.iterable-"><code>string.join(charsequence delimiter, iterable<? extends charsequence> elements)</code></a> </p><pre><code>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" </code></pre>

<p>вы могли бы написать небольшой вспомогательный метод в стиле соединения, который работает на java.util.lists</p><pre><code>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(); } </code></pre><p>тогда используйте это так:</p><pre><code> list<string> list = new arraylist<string>(); if( condition ) list.add("elementname"); if( anothercondition ) list.add("anotherelementname"); join(list, ","); </code></pre>

<p>в случае android класс stringutils из общего достояния недоступен, поэтому для этого я использовал</p><pre><code>android.text.textutils.join(charsequence delimiter, iterable tokens) </code></pre><p> <a href="http://developer.android.com/reference/android/text/textutils.html">http://developer.android.com/reference/android/text/textutils.html</a> </p>

<p> <a href="http://code.google.com/p/guava-libraries/">библиотека google guava</a> имеет <strong> <em> <a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/joiner.html">com.google.common.base.joiner</a> </em> </strong> класс, который помогает решать такие задачи.</p><p>образцы:</p><pre><code>"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" </code></pre><hr><p>вот <a href="http://code.google.com/p/guava-libraries/wiki/stringsexplained">статья о строковых утилитах guava</a> .</p>

<p>в java 8 вы можете использовать <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/string.html#join-java.lang.charsequence-java.lang.iterable-"><code>string.join()</code></a> :</p><pre><code>list<string> list = arrays.aslist("foo", "bar", "baz"); string joined = string.join(" and ", list); // "foo and bar and baz" </code></pre><p>также взгляните на <a href="https://stackoverflow.com/a/22577565/1115554">этот ответ</a> для примера stream api.</p>

<p>вы можете обобщить это, но в java нет объединения, как вы хорошо сказали.</p><p> <a href="http://snippets.dzone.com/posts/show/91">это</a> может работать лучше.</p><pre><code>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(); } </code></pre>

<p>используйте подход, основанный на <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/stringbuilder.html"><code>java.lang.stringbuilder</code></a> ! ("изменчивая последовательность символов.")</p><p>как вы упомянули, все эти объединения строк создают строки повсюду. <code>stringbuilder</code> не будет этого делать.</p><p>почему <code>stringbuilder</code> вместо <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/stringbuffer.html"><code>stringbuffer</code></a> ? из <code>stringbuilder</code> javadoc:</p><blockquote> <p>там, где это возможно, рекомендуется использовать этот класс вместо stringbuffer, так как он будет быстрее в большинстве реализаций.</p> </blockquote>

<p>в java 8 вы можете сделать это следующим образом:</p><pre><code>list.stream().map(object::tostring) .collect(collectors.joining(delimiter)); </code></pre><p>если в списке есть нули, вы можете использовать:</p><pre><code>list.stream().map(string::valueof) .collect(collectors.joining(delimiter)) </code></pre>

<p>я бы использовал google collections. в отеле есть nice connect.<br> <a href="http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/join.html">http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/join.html</a> </p><p>но если бы я хотел написать это сам,</p><pre><code>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); } } </code></pre><p>я думаю, что это лучше работает с коллекцией объектов, так как теперь вам не нужно конвертировать ваши объекты в строки, прежде чем присоединиться к ним.</p>

<p> <a href="http://commons.apache.org/">apache commons</a> у класса stringutils есть метод join.</p>

<h2>java 8</h2><pre><code>stringcollection.stream().collect(collectors.joining(", ")); </code></pre>

<p>и минимальный (если вы не хотите включать apache commons против guava в зависимости проекта только для объединения строк)</p><pre><code>/** * * @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(); } </code></pre>

<p>используйте stringbuilder и класс <code>separator</code></p><pre><code>stringbuilder buf = new stringbuilder(); separator sep = new separator(", "); for (string each : list) { buf.append(sep).append(each); } </code></pre><p>разделитель переносит разделитель. разделитель возвращается методом <code>tostring</code> separator, если только при первом вызове, который возвращает пустую строку!</p><p>исходный код для класса <code>separator</code></p><pre><code>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; } } </code></pre>

<p>для этого вы можете использовать тип <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/stringbuilder.html"><code>stringbuilder</code></a> java. также есть <code>stringbuffer</code>, но он содержит дополнительную логику безопасности потоков, которая часто не нужна.</p>

<p>если вы используете <a href="https://www.eclipse.org/collections/">eclipse collections</a> , вы можете использовать <code>makestring()</code> или <code>appendstring()</code>.</p><p><code>makestring()</code> возвращает представление <code>string</code>, аналогичное <code>tostring()</code>.</p><p>имеет три формы</p><ul> <li><code>makestring(start, separator, end)</code></li> <li><code>makestring(separator)</code> по умолчанию начало и конец пустые строки</li> <li><code>makestring()</code> по умолчанию разделитель <code>", "</code> (запятая и пробел)</li> </ul><p>пример кода:</p><pre><code>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("[", ", ", "]")); </code></pre><p><code>appendstring()</code> похож на <code>makestring()</code>, но он добавляет к <code>appendable</code> (например, <code>stringbuilder</code>) и является <code>void</code>. он имеет те же три формы, с дополнительным первым аргументом, "добавляемый".</p><pre><code>mutablelist<integer> list = fastlist.newlistwith(1, 2, 3); appendable appendable = new stringbuilder(); list.appendstring(appendable, "[", "/", "]"); assertequals("[1/2/3]", appendable.tostring()); </code></pre><p>если вы не можете преобразовать свою коллекцию в тип коллекций eclipse, просто адаптируйте ее с помощью соответствующего адаптера.</p><pre><code>list<object> list = ...; listadapter.adapt(list).makestring(","); </code></pre><p><strong> примечание: </strong> я коммиттер для коллекций eclipse.</p>

<p>если вы используете spring mvc, вы можете попробовать следующие шаги.</p><pre><code>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()); </code></pre><p>это приведет к <code>a,b,c</code></p>

<p>почему бы не написать свой собственный метод join ()? в качестве параметров он будет принимать коллекцию strings и разделитель string. внутри метода переберите коллекцию и создайте свой результат в stringbuffer.</p>

<p>с java 5 переменными аргументами, так что вам не нужно явно вставлять все свои строки в коллекцию или массив:</p><pre><code>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")); } } </code></pre>

<p>родной тип java 8</p><pre><code>list<integer> example; example.add(1); example.add(2); example.add(3); ... example.stream().collect(collectors.joining(",")); </code></pre><p>java 8 custom object:</p><pre><code>list<person> person; ... person.stream().map(person::getage).collect(collectors.joining(",")); </code></pre>

<p>для тех, кто находится в контексте spring, их класс <a href="http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/stringutils.html">stringutils</a> также полезен:</p><p>есть много полезных ярлыков, таких как:</p><ul> <li>collectiontocommadelimitedstring (collection coll)</li> <li>collectiontodelimitedstring (коллекция coll, разделитель строк)</li> <li>arraytodelimitedstring (object [] arr, string delim)</li> </ul><p>и много других.</p><p>это может быть полезно, если вы еще не используете java 8 и уже находитесь в контексте spring.</p><p>я предпочитаю это против apache commons (хотя и очень хорошо) для поддержки коллекции, которая проще как это:</p><pre><code>// 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); </code></pre>

<p>вам, вероятно, следует использовать <code>stringbuilder</code> с методом <code>append</code> для построения вашего результата, но в остальном это такое же хорошее решение, как и java.</p>

<p>почему бы вам не сделать в java то же самое, что вы делаете в ruby, то есть создание разделенной разделителем строки только после того, как вы добавили все части в массив?</p><pre><code>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"; } </code></pre><p>вы можете переместить цикл for в отдельный вспомогательный метод, а также использовать stringbuilder вместо stringbuffer ...</p><p><em>edit</em>: исправлен порядок добавления.</p>

<p>в общем, как-то так:</p><pre><code>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(); } } </code></pre>

<p>не знаю, действительно ли это лучше, но, по крайней мере, он использует stringbuilder, который может быть немного более эффективным.</p><p>ниже приведен более общий подход, если вы можете создать список параметров до того, как выполните какой-либо разделитель параметров.</p><pre><code>// 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"); } </code></pre>

<p>ваш подход не так уж плох, но вы должны использовать stringbuffer вместо знака +. + имеет большой недостаток в том, что для каждой отдельной операции создается новый экземпляр string. чем длиннее ваша строка, тем больше накладные расходы. поэтому использование stringbuffer должно быть самым быстрым способом:</p><pre><code>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; } } </code></pre><p>после того, как вы закончили создание вашей строки, просто вызовите tostring () для возвращенного stringbuffer.</p>

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

<p>использовать <a href="http://bitbucket.org/dfa/dollar">доллар</a> просто, как набрать:</p><pre><code>string joined = $(acollection).join(","); </code></pre><p>nb: это работает также для массива и других типов данных</p><h2>реализация</h2><p>внутренне он использует очень аккуратный трюк:</p><pre><code>@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(); } </code></pre><p>класс <code>separator</code> возвращает пустую строку только при первом ее вызове, а затем возвращает разделитель:</p><pre><code>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; } } } </code></pre>

<p>исправить ответ роб дикерсон.</p><p>это проще в использовании:</p><pre><code>public static string join(string delimiter, string... values) { stringbuilder stringbuilder = new stringbuilder(); for (string value : values) { stringbuilder.append(value); stringbuilder.append(delimiter); } string result = stringbuilder.tostring(); return result.isempty() ? result : result.substring(0, result.length() - 1); } </code></pre>

<p>вместо использования конкатенации строк вы должны использовать stringbuilder, если ваш код не является многопоточным, и stringbuffer, если он есть.</p>

<p>итак, пара вещей, которые вы могли бы сделать, чтобы создать ощущение, будто вы ищете:</p><p>1) расширьте класс list - и добавьте в него метод join. метод join будет просто выполнять конкатенацию и добавление разделителя (который может быть параметром метода join)</p><p>2) похоже, java 7 будет добавлять методы расширения к java, что позволит вам просто прикрепить определенный метод к классу: чтобы вы могли написать этот метод соединения и добавить его в качестве метода расширения в список или даже в коллекцию.</p><p>решение 1, вероятно, является единственным реалистичным, хотя, поскольку java 7 еще не вышло :) но оно должно работать просто отлично.</p><p>чтобы использовать оба из них, вы просто добавляете все свои элементы в список или коллекцию как обычно, а затем вызываете новый пользовательский метод, чтобы "присоединиться" к ним.</p>

<p>вы делаете это немного сложнее, чем должно быть. давайте начнем с конца вашего примера:</p><pre><code>string parameterstring = ""; if ( condition ) parameterstring = appendwithdelimiter( parameterstring, "elementname", "," ); if ( anothercondition ) parameterstring = appendwithdelimiter( parameterstring, "anotherelementname", "," ); </code></pre><p>с небольшим изменением использования stringbuilder вместо string это становится:</p><pre><code>stringbuilder parameterstring = new stringbuilder(); if (condition) parameterstring.append("elementname").append(","); if (anothercondition) parameterstring.append("anotherelementname").append(","); ... </code></pre><p>когда вы закончите (я предполагаю, что вы должны проверить и несколько других условий), просто убедитесь, что вы удалили запятую с помощью такой команды:</p><pre><code>if (parameterstring.length() > 0) parameterstring.deletecharat(parameterstring.length() - 1); </code></pre><p>и наконец, получите строку, которую вы хотите с</p><pre><code>parameterstring.tostring(); </code></pre><p>вы также можете заменить "," во втором вызове, чтобы добавить общую строку разделителя, которая может быть установлена на что угодно. если у вас есть список вещей, которые, как вы знаете, нужно добавить (безоговорочно), вы можете поместить этот код в метод, который принимает список строк.</p>

<p>небольшое улучшение [скорость] версии от izb:</p><pre><code>public static string join(string[] strings, char del) { stringbuilder sb = new stringbuilder(); int len = strings.length; if(len > 1) { len -= 1; }else { return strings[0]; } for (int i = 0; i < len; i++) { sb.append(strings[i]).append(del); } sb.append(strings[i]); return sb.tostring(); } </code></pre>

<p>лично я довольно часто использую следующее простое решение для целей регистрации:</p><pre><code>list lst = arrays.aslist("ab", "bc", "cd"); string str = lst.tostring().replaceall("[\\[\\]]", ""); </code></pre>

<p>вы можете попробовать что-то вроде этого:</p><pre><code>stringbuilder sb = new stringbuilder(); if (condition) { sb.append("elementname").append(","); } if (anothercondition) { sb.append("anotherelementname").append(","); } string parameterstring = sb.tostring(); </code></pre>