Java: преобразование List ‹String› в строку

В JavaScript есть Array.join()

js>["Bill","Bob","Steve"].join(" and ")
Bill and Bob and Steve

Есть ли в Java что-нибудь подобное? Я знаю, что могу что-нибудь придумать с помощью StringBuilder:

static public String join(List<String> list, String conjunction)
{
   StringBuilder sb = new StringBuilder();
   boolean first = true;
   for (String item : list)
   {
      if (first)
         first = false;
      else
         sb.append(conjunction);
      sb.append(item);
   }
   return sb.toString();
}

... но нет смысла это делать, если что-то подобное уже является частью JDK.


person Jason S    schedule 17.11.2009    source источник
comment
См. Также этот вопрос для списков   -  person Casebash    schedule 11.08.2010
comment
Не совсем связанные, но в Android есть встроенная функция соединения как часть их класса TextUtils: developer.android.com/reference/android/text/, java.lang.Iterable)   -  person Xavi    schedule 31.05.2013
comment
В Java 8 есть метод String.join(). Взгляните на этот ответ, если вы используете Java 8 (или новее) stackoverflow.com/a/22577565/1115554   -  person micha    schedule 22.03.2014


Ответы (22)


С Java 8 вы можете сделать это без какой-либо сторонней библиотеки.

Если вы хотите присоединиться к Коллекции строк, вы можете использовать новый String.join ():

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

Если у вас есть коллекция с типом, отличным от String, вы можете использовать Stream API с присоединение к Collector:

List<Person> list = Arrays.asList(
  new Person("John", "Smith"),
  new Person("Anna", "Martinez"),
  new Person("Paul", "Watson ")
);

String joinedFirstNames = list.stream()
  .map(Person::getFirstName)
  .collect(Collectors.joining(", ")); // "John, Anna, Paul"

Также может быть полезен класс StringJoiner.

person micha    schedule 22.03.2014
comment
К сожалению, String.join принимает только CharSequences, а не объекты, как можно было бы надеяться. Даже не уверен, что это нуль-безопасно - person Marc; 22.06.2015
comment
@Marc assertThat(String.join(", ", Lists.newArrayList("1", null)), is("1, null")); - person Sanghyun Lee; 09.12.2015
comment
StringJoiner особенно полезен, если кто-то хочет присоединиться к чему-то вроде [a, b, c], включая фигурные скобки. - person koppor; 17.02.2016
comment
@Marc String.join также принимает Iterable<CharSequence>; Взаимосвязь интерфейсов: Iterable -> Collection -> List - person aff; 13.03.2019

Все ссылки на Apache Commons в порядке (и это то, что использует большинство людей), но я думаю, что Guava эквивалент, Joiner имеет гораздо более приятный API.

Вы можете сделать простой случай соединения с

Joiner.on(" and ").join(names)

но также легко справиться с нулями:

Joiner.on(" and ").skipNulls().join(names);

or

Joiner.on(" and ").useForNull("[unknown]").join(names);

и (достаточно полезно, насколько я понимаю, чтобы использовать его вместо commons-lang), возможность работать с картами:

Map<String, Integer> ages = .....;
String foo = Joiner.on(", ").withKeyValueSeparator(" is ").join(ages);
// Outputs:
// Bill is 25, Joe is 30, Betty is 35

что чрезвычайно полезно для отладки и т. д.

person Cowan    schedule 18.11.2009
comment
спасибо за вилку! Наш Joiner также дает вам возможность напрямую добавлять в Appendable (например, StringBuilder или любой Writer) без создания промежуточных строк, которых, похоже, не хватает библиотеке Apache. - person Kevin Bourrillion; 18.11.2009
comment
Это здорово, но не могли бы вы добавить .useForLastSeparator() или аналогичный метод? Таким образом, вы можете получить что-то вроде и только между двумя последними элементами (с остальными). - person Jeff Evans; 07.05.2013
comment
Да, это потокобезопасный. Единственное состояние, сохраненное в соединителе, - это separator (то есть final). Все, что я видел в Guava, где я использовал эквивалент Apache Commons, было намного лучше (читай: чище, быстрее, безопаснее и просто более надежно в целом) в Guava с меньшим количеством сбоев крайнего случая и проблемами безопасности потоков и меньший объем памяти. Их наилучший возможный руководящий принцип, кажется, пока остается верным. - person Shadow Man; 28.03.2014
comment
Если вам нужно пойти в противоположном направлении (т.е. разделить строку на части), обратите внимание на разделитель класса Guava - также очень хорошо спроектированный / реализованный. - person Matt Passell; 01.07.2014
comment
В Java 8 есть String.join() метод и StringJoiner класс. - person Sheikh; 20.12.2014
comment
Красиво, но все равно читается странно: useForNull("[unknown]") должно быть forNullUse("[unknown]"), не так ли? - person Thomas Weller; 20.04.2015
comment
Чтобы пропустить пустые значения, используйте Strings.emptyToNull в своих значениях: final String a = Strings.emptyToNull( nameA.trim() ); return Joiner.on(" and ").skipNulls().join(a); - person pyb; 30.07.2015
comment
Ага, читается странно, обратное было бы более читаемым (поскольку оно работает с массивом) Joiner.on (names) .skipNulls (). Join (and); - person holdfenytolvaj; 14.04.2016

Не из коробки, но во многих библиотеках есть похожие:

Commons Lang:

org.apache.commons.lang.StringUtils.join(list, conjunction);

Весна:

org.springframework.util.StringUtils.collectionToDelimitedString(list, conjunction);
person Arne Burmeister    schedule 17.11.2009

На Android вы можете использовать класс TextUtils.

TextUtils.join(" and ", names);
person Rafal Enden    schedule 01.09.2015
comment
Я искал это. String.join требуется минимальный уровень API 26 для Android. - person okarakose; 15.01.2018

Нет, в стандартном Java API такого удобного метода нет.

Неудивительно, что Apache Commons предоставляет такую ​​возможность в их классе StringUtils на тот случай, если вы не хотите писать его самостоятельно.

person Bart Kiers    schedule 17.11.2009
comment
Меня всегда беспокоило, что String имеет разделение, но не соединение. В каком-то смысле это имеет смысл, но меня просто раздражает, что для этого нет хотя бы статического метода в String. - person Powerlord; 18.11.2009
comment
Я с тобой, Бемроуз. По крайней мере, они дали нам isEmpty() метод вместо (статического) join() метода в _3 _...: rollseyes: :) - person Bart Kiers; 18.11.2009

Три возможности в Java 8:

List<String> list = Arrays.asList("Alice", "Bob", "Charlie")

String result = String.join(" and ", list);

result = list.stream().collect(Collectors.joining(" and "));

result = list.stream().reduce((t, u) -> t + " and " + u).orElse("");
person amra    schedule 16.04.2015

С коллектором java 8 это можно сделать с помощью следующего кода:

Arrays.asList("Bill", "Bob", "Steve").stream()
.collect(Collectors.joining(" and "));

Также самое простое решение в java 8:

String.join(" and ", "Bill", "Bob", "Steve");

or

String.join(" and ", Arrays.asList("Bill", "Bob", "Steve"));
person aifa    schedule 23.11.2014

Я написал это (я использую его для beans и использую toString, поэтому не пишите Collection<String>):

public static String join(Collection<?> col, String delim) {
    StringBuilder sb = new StringBuilder();
    Iterator<?> iter = col.iterator();
    if (iter.hasNext())
        sb.append(iter.next().toString());
    while (iter.hasNext()) {
        sb.append(delim);
        sb.append(iter.next().toString());
    }
    return sb.toString();
}

но Collection не поддерживается JSP, поэтому для TLD я написал:

public static String join(List<?> list, String delim) {
    int len = list.size();
    if (len == 0)
        return "";
    StringBuilder sb = new StringBuilder(list.get(0).toString());
    for (int i = 1; i < len; i++) {
        sb.append(delim);
        sb.append(list.get(i).toString());
    }
    return sb.toString();
}

и поместите в .tld файл:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
    <function>
        <name>join</name>
        <function-class>com.core.util.ReportUtil</function-class>
        <function-signature>java.lang.String join(java.util.List, java.lang.String)</function-signature>
    </function>
</taglib>

и используйте его в файлах JSP как:

<%@taglib prefix="funnyFmt" uri="tag:com.core.util,2013:funnyFmt"%>
${funnyFmt:join(books, ", ")}
person gavenkoa    schedule 05.04.2013
comment
предложить изменить Collection<> на Iterable<>? - person Jason S; 07.04.2013
comment
@JasonS +1. Хороший замечание, но прочтите stackoverflow.com/questions/1159797/ - person gavenkoa; 08.04.2013

Код, который у вас есть, - это правильный способ сделать это, если вы хотите использовать JDK без каких-либо внешних библиотек. Не существует простого однострочника, который можно было бы использовать в JDK.

Если вы можете использовать внешние библиотеки, я рекомендую вам изучить org.apache.commons.lang.StringUtils в библиотеке Apache Commons.

Пример использования:

List<String> list = Arrays.asList("Bill", "Bob", "Steve");
String joinedResult = StringUtils.join(list, " and ");
person Juha Syrjälä    schedule 17.11.2009

Ортодоксальный способ добиться этого - определить новую функцию:

public static String join(String joinStr, String... strings) {
    if (strings == null || strings.length == 0) {
        return "";
    } else if (strings.length == 1) {
        return strings[0];
    } else {
        StringBuilder sb = new StringBuilder(strings.length * 1 + strings[0].length());
        sb.append(strings[0]);
        for (int i = 1; i < strings.length; i++) {
            sb.append(joinStr).append(strings[i]);
        }
        return sb.toString();
    }
}

Образец:

String[] array = new String[] { "7, 7, 7", "Bill", "Bob", "Steve",
        "[Bill]", "1,2,3", "Apple ][","~,~" };

String joined;
joined = join(" and ","7, 7, 7", "Bill", "Bob", "Steve", "[Bill]", "1,2,3", "Apple ][","~,~");
joined = join(" and ", array); // same result

System.out.println(joined);

Выход:

7, 7, 7 и Билл, и Боб, и Стив, и [Билл], и 1,2,3, и Apple] [и ~, ~

person Daniel De León    schedule 22.07.2013
comment
давать параметру метода то же имя, что и самому методу, вероятно, не лучшая идея. separator было бы намного более наводящим на размышления. - person ccpizza; 09.12.2015

Решение Java 8 с java.util.StringJoiner

В Java 8 есть класс StringJoiner. Но вам все равно нужно написать немного шаблона, потому что это Java.

StringJoiner sj = new StringJoiner(" and ", "" , "");
String[] names = {"Bill", "Bob", "Steve"};
for (String name : names) {
   sj.add(name);
}
System.out.println(sj);
person klingt.net    schedule 10.09.2014
comment
Вам не нужно писать немного шаблонов, если вы используете более удобный метод String.join (). - person Andy Thomas; 19.10.2018

Вы можете использовать библиотеку apache commons, в которой есть класс StringUtils и метод соединения.

Проверьте эту ссылку: https://commons.apache.org/proper/commons-lang/javadocs/api.2.0/org/apache/commons/lang/StringUtils.html

Обратите внимание, что ссылка выше может со временем устареть, и в этом случае вы можете просто поискать в Интернете «apache commons StringUtils», что должно позволить вам найти последнюю ссылку.

(ссылка на этот поток) Java-эквиваленты C # String.Format () и String.Join ()

person dcp    schedule 17.11.2009

Ты можешь сделать это:

String aToString = java.util.Arrays.toString(anArray);
// Do not need to do this if you are OK with '[' and ']'
aToString = aToString.substring(1, aToString.length() - 1);

Или однострочный (только если вам не нужны '[' и ']')

String aToString = java.util.Arrays.toString(anArray).substring(1).replaceAll("\\]$", "");

Надеюсь это поможет.

person NawaMan    schedule 17.11.2009
comment
Это не добавит конъюнкции выбора. - person hexium; 18.11.2009
comment
Упс !! Извините, я этого не заметил. - person NawaMan; 18.11.2009

Интересный способ сделать это с помощью чистого JDK в одной строке:

String[] array = new String[] { "Bill", "Bob", "Steve","[Bill]","1,2,3","Apple ][" };
String join = " and ";

String joined = Arrays.toString(array).replaceAll(", ", join)
        .replaceAll("(^\\[)|(\\]$)", "");

System.out.println(joined);

Выход:

Билл и Боб, Стив и [Билл], 1,2,3 и Apple] [


Не слишком идеальный и не слишком увлекательный способ!

String[] array = new String[] { "7, 7, 7","Bill", "Bob", "Steve", "[Bill]",
        "1,2,3", "Apple ][" };
String join = " and ";

for (int i = 0; i < array.length; i++) array[i] = array[i].replaceAll(", ", "~,~");
String joined = Arrays.toString(array).replaceAll(", ", join)
        .replaceAll("(^\\[)|(\\]$)", "").replaceAll("~,~", ", ");

System.out.println(joined);

Выход:

7, 7, 7 и Билл, и Боб, и Стив, и [Билл], и 1,2,3, и Apple] [

person Daniel De León    schedule 20.07.2013
comment
Попробуйте это с [Bill], 1,2,3 и Apple] [. Креативный, но есть случаи, когда это неверно. - person Jason S; 22.07.2013
comment
Теперь попробуйте с ~, ~. Вы не можете сделать программу с такой архитектурой полностью пуленепробиваемой. - person Jason S; 22.07.2013
comment
Да, я знаю ... Я убрал голос против. Но вам следует подумать немного более внимательно о размещении здесь ответов, особенно на такие вопросы, как этот, которым уже несколько лет. Мало того, что ответы сразу же пригодятся исходному постеру, они также появятся в поиске Google. (Фактически, для вопросов, которым несколько лет, новые ответы могут иметь не ценности для исходного плаката.) Решения, содержащие недостатки или ошибки, могут быть вредными для людей, которые позже обнаружат их вне контекста. - person Jason S; 22.07.2013
comment
Я многому научился здесь из других сообщений, подобных моему, с не идеальным решением, но действительно богатым знаниями и сторонним мышлением. Расслабьтесь для других, каждый должен понимать, что делает каждая строка своего кода. Помните, что программирование - это искусство, и даже каждая строчка кода что-то говорит о личности программиста. И да, я не буду использовать этот код в продакшене! - person Daniel De León; 22.07.2013

Возможно, вы захотите попробовать метод соединения Apache Commons StringUtils:

http://commons.apache.org/lang/api/org/apache/commons/lang/StringUtils.html#join(java.util.Iterator, java.lang.String)

Я обнаружил, что Apache StringUtils подбирает слабину jdk ;-)

person Upgradingdave    schedule 17.11.2009

Если вы используете Коллекции Eclipse (ранее GS Collections), вы можете использовать метод makeString().

List<String> list = Arrays.asList("Bill", "Bob", "Steve");

String string = ListAdapter.adapt(list).makeString(" and ");

Assert.assertEquals("Bill and Bob and Steve", string);

Если вы можете преобразовать свой List в тип Коллекции Eclipse, вы можете избавиться от адаптера.

MutableList<String> list = Lists.mutable.with("Bill", "Bob", "Steve");
String string = list.makeString(" and ");

Если вам просто нужна строка, разделенная запятыми, вы можете использовать версию makeString(), которая не принимает параметров.

Assert.assertEquals(
    "Bill, Bob, Steve", 
    Lists.mutable.with("Bill", "Bob", "Steve").makeString());

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

person Craig P. Motlin    schedule 13.11.2013

ИЗМЕНИТЬ

Я также заметил toString() основную проблему реализации и элемент, содержащий разделитель, но я думал, что я параноик.

Поскольку у меня есть два комментария по этому поводу, я изменяю свой ответ на:

static String join( List<String> list , String replacement  ) {
    StringBuilder b = new StringBuilder();
    for( String item: list ) { 
        b.append( replacement ).append( item );
    }
    return b.toString().substring( replacement.length() );
}

Что очень похоже на исходный вопрос.

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

Я думаю, что в вашем исходном коде нет ничего плохого. На самом деле альтернатива, которую предлагают все, выглядит почти одинаково (хотя и требует ряда дополнительных проверок).

Вот он вместе с лицензией Apache 2.0.

public static String join(Iterator iterator, String separator) {
    // handle null, zero and one elements before building a buffer
    if (iterator == null) {
        return null;
    }
    if (!iterator.hasNext()) {
        return EMPTY;
    }
    Object first = iterator.next();
    if (!iterator.hasNext()) {
        return ObjectUtils.toString(first);
    }

    // two or more elements
    StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
    if (first != null) {
        buf.append(first);
    }

    while (iterator.hasNext()) {
        if (separator != null) {
            buf.append(separator);
        }
        Object obj = iterator.next();
        if (obj != null) {
            buf.append(obj);
        }
    }
    return buf.toString();
}

Теперь мы знаем, спасибо, открытый исходный код

person OscarRyz    schedule 17.11.2009
comment
Сейчас это будет работать, но вы не можете быть уверены, как List.toString () будет вести себя в будущем. Я бы никогда не стал доверять операции toString (), кроме печати некоторой информационной строки о рассматриваемом объекте. В свою защиту, вы не единственный, кто предлагает это решение. - person Hans Doggen; 18.11.2009
comment
Что, если содержимое элемента в списке содержит подстроку ", "? - person Bart Kiers; 18.11.2009
comment
@ Ганс и @ Барт: Вы правы. Думал, никто больше не заметит: P Я меняю свой ответ. - person OscarRyz; 18.11.2009
comment
Это зависит от варианта использования. Для целей отладки toString () отлично подходит IMO ... при условии, что вы не полагаетесь на конкретное форматирование. И даже в этом случае, если вы напишете тесты для своего кода, любые будущие изменения будут обнаружены. - person akostadinov; 28.10.2012

С java 1.8 можно использовать поток,

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> list = Arrays.asList("Bill","Bob","Steve").
String str = list.stream().collect(Collectors.joining(" and "));
person Saurabh    schedule 09.06.2018

Google Guava API также имеет .join (), хотя (как должно быть очевидно из других ответов) Apache Commons здесь в значительной степени является стандартом.

person Dean J    schedule 17.11.2009

Java 8 приносит

Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

метод, который является нулевым за счет использования prefix + suffix для нулевых значений.

Его можно использовать следующим образом:

String s = stringList.stream().collect(Collectors.joining(" and ", "prefix_", "_suffix"))

Метод Collectors.joining(CharSequence delimiter) просто вызывает joining(delimiter, "", "") внутри.

person thg    schedule 12.03.2018

Вы можете использовать это из StringUtils Spring Framework. Я знаю, что об этом уже упоминалось, но на самом деле вы можете просто взять этот код, и он сразу заработает, без использования Spring.

// from https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/StringUtils.java

/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class StringUtils {
    public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) {
        if(coll == null || coll.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        Iterator<?> it = coll.iterator();
        while (it.hasNext()) {
            sb.append(prefix).append(it.next()).append(suffix);
            if (it.hasNext()) {
                sb.append(delim);
            }
        }
        return sb.toString();
    }
}
person EpicPandaForce    schedule 16.01.2017

Попробуй это:

java.util.Arrays.toString(anArray).replaceAll(", ", ",")
                .replaceFirst("^\\[","").replaceFirst("\\]$","");
person jorge    schedule 02.09.2013
comment
Вероятно, потому что вопрос требует использования списка вместо массива? пожать плечами - person Nick Coelius; 03.12.2013
comment
Потому что, если у вас есть, в ваших строках это не сработает. - person Cyrille Pontvieux; 03.08.2015