Каков наилучший способ создания строки разделенных элементов в 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, ",");
Ответ 3
В случае Android класс StringUtils из сообщества недоступен, поэтому для этого я использовал
android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)
http://developer.android.com/reference/android/text/TextUtils.html
Ответ 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))
Ответ 10
Сообщество Apache Класс StringUtils имеет метод объединения.
Ответ 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;
}
}
}