Каков наилучший способ построить строку элементов с разделителями в 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?
37 ответов
До 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"
Вы можете написать небольшой метод-метод в стиле соединения, который работает с 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, ",");
В случае Android класс StringUtils из общего достояния недоступен, поэтому для этого я использовал
android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)
http://developer.android.com/reference/android/text/TextUtils.html
В библиотеке 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"
В 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.
В Java 8 вы можете сделать это следующим образом:
list.stream().map(Object::toString)
.collect(Collectors.joining(delimiter));
если в списке есть нули, вы можете использовать:
list.stream().map(String::valueOf)
.collect(Collectors.joining(delimiter))
Вы можете обобщить это, но в 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();
}
Используйте подход, основанный на java.lang.StringBuilder
! ("Изменчивая последовательность символов. ")
Как вы упомянули, все эти объединения строк создают строки повсюду. StringBuilder
не буду этого делать
Зачем StringBuilder
вместо StringBuffer
? От StringBuilder
Javadoc:
Там, где это возможно, рекомендуется использовать этот класс вместо StringBuffer, так как он будет быстрее в большинстве реализаций.
Я бы использовал 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);
}
}
Я думаю, что это лучше работает с коллекцией объектов, так как теперь вам не нужно конвертировать ваши объекты в строки, прежде чем присоединиться к ним.
Родной тип 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(","));
Java 8
stringCollection.stream().collect(Collectors.joining(", "));
Используйте 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;
}
}
И минимальный (если вы не хотите включать 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();
}
Если вы используете 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
Вы можете использовать Java StringBuilder
типа для этого. Есть также StringBuffer
, но он содержит дополнительную логику безопасности потока, которая часто не нужна.
Почему бы не написать свой собственный метод join()? В качестве параметров он будет принимать коллекцию Strings и разделитель String. Внутри метода переберите коллекцию и создайте свой результат в StringBuffer.
Если вы используете 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.
Для тех, кто находится в контексте 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);
Вы, вероятно, должны использовать StringBuilder
с append
метод для построения вашего результата, но в остальном это такое же хорошее решение, как и Java.
Почему бы вам не сделать в 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...
Редактировать: исправлен порядок добавления.
Если вы хотите применить запятую в Списке свойств объекта. Это способ, который я нашел наиболее полезным.
здесь getName () - строковое свойство класса, к которому я пытался добавить ",".
Строка message = listName.stream (). Map (список -> list.getName()). Collect (Collectors.joining(", "));
Благодаря переменным аргументам 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"));
}
}
Вы делаете это немного сложнее, чем должно быть. Давайте начнем с конца вашего примера:
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();
Вы также можете заменить "," во втором вызове, чтобы добавить общую строку разделителя, которая может быть установлена на что угодно. Если у вас есть список вещей, которые, как вы знаете, нужно добавить (безоговорочно), вы можете поместить этот код в метод, который принимает список строк.
Итак, пара вещей, которые вы могли бы сделать, чтобы создать ощущение, будто вы ищете:
1) Расширьте класс List - и добавьте к нему метод соединения. Метод join будет просто выполнять конкатенацию и добавление разделителя (который может быть параметром метода join)
2) Похоже, что в Java 7 будут добавлены методы расширения в java, что позволит вам просто прикрепить конкретный метод к классу: чтобы вы могли написать этот метод соединения и добавить его в качестве метода расширения в List или даже в Коллекция.
Решение 1, вероятно, является единственным реалистичным, хотя, поскольку Java 7 еще не выпущена:) Но он должен работать просто отлично.
Чтобы использовать оба из них, вы просто добавляете все свои элементы в Список или Коллекцию как обычно, а затем вызываете новый пользовательский метод, чтобы "присоединиться" к ним.
Ваш подход не так уж плох, но вы должны использовать 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.
//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();
Использовать 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
возвращать пустую строку только в первый раз, когда она вызывается, затем она возвращает разделитель:
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;
}
}
}
Вместо использования конкатенации строк вы должны использовать StringBuilder, если ваш код не является многопоточным, и StringBuffer, если он есть.
Исправить ответ Роб Дикерсон.
Это проще в использовании:
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);
}