Java: przekonwertuj List <String> na String

595

JavaScript ma Array.join()

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

Czy Java ma coś takiego? Wiem, że mogę sobie coś ułożyć z 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();
}

... ale nie ma sensu tego robić, jeśli coś takiego jest już częścią JDK.

Jason S.
źródło
1
Zobacz także to pytanie dotyczące list
Casebash,
18
Nie jest ściśle powiązany, ale Android ma wbudowaną funkcję łączenia w ramach klasy TextUtils: developer.android.com/reference/android/text/… , java.lang.Iterable)
Xavi
16
Java 8 ma String.join()metodę. Spójrz na tę odpowiedź, jeśli używasz Java 8 (lub nowszej) stackoverflow.com/a/22577565/1115554
micha

Odpowiedzi:

763

W Javie 8 możesz to zrobić bez biblioteki innej firmy.

Jeśli chcesz dołączyć do kolekcji ciągów, możesz użyć nowej metody String.join () :

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

Jeśli masz kolekcję innego typu niż String, możesz użyć Stream API z dołączającym 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"

StringJoinerKlasy mogą być również użyteczne.

Micha
źródło
7
Niestety, String.join akceptuje tylko CharSequences, a nie tak, jak można by oczekiwać od Objects. Nie jestem nawet pewien, czy to nie jest bezpieczne
Marc
@MarcassertThat(String.join(", ", Lists.newArrayList("1", null)), is("1, null"));
Sanghyun Lee,
StringJoiner jest szczególnie przydatny, jeśli chcemy dołączyć do czegoś takiego [a, b, c]jak nawiasy klamrowe.
koppor
@Marc String.join również akceptuje Iterable<CharSequence>; Relacja interfejsu:Iterable -> Collection -> List
aff
306

Wszystkie odniesienia do Apache Commons są w porządku (i tego używa większość ludzi), ale myślę, że odpowiednik Guava , Joiner , ma znacznie ładniejszy interfejs API.

Możesz zrobić prosty przypadek łączenia za pomocą

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

ale również łatwo radzi sobie z zerami:

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

lub

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

oraz (na tyle przydatne, o ile mnie to interesuje, aby używać go zamiast commons-lang), możliwość radzenia sobie z Mapami:

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

co jest niezwykle przydatne do debugowania itp.

Cowan
źródło
10
dzięki za wtyczkę! Nasz Joiner daje również opcję dołączania bezpośrednio do Appendable (takiego jak StringBuilder lub dowolny Writer) bez tworzenia pośrednich ciągów, których wydaje się brakować w bibliotece Apache.
Kevin Bourrillion,
2
To świetnie, ale czy możesz dodać .useForLastSeparator()podobną metodę? W ten sposób możesz uzyskać coś w rodzaju „i” między tylko dwoma ostatnimi elementami (z resztą za pomocą „,”).
Jeff Evans
2
Tak, jest bezpieczny dla wątków. Jedynym stanem zapisanym w łączeniu jest separator(który jest final). Wszystko, co widziałem na Guava, gdzie korzystałem z odpowiednika Apache Commons, było o wiele lepsze (czytaj: czystsze, szybsze, bezpieczniejsze i po prostu bardziej solidne w ogóle) na Guava z mniejszą liczbą awarii krawędzi i problemów z bezpieczeństwem wątków i mniejszy ślad pamięci. Wydaje się, że ich zasada przewodnia „najlepszego możliwego sposobu” jest prawdziwa.
Shadow Man
Jeśli potrzebujesz iść w przeciwnym kierunku (tj. Dzielenie sznurka na kawałki), sprawdź Splitter klasy Guava - również bardzo dobrze zaprojektowany / wdrożony.
Matt Passell
W Javie 8 istnieje String.join()metoda i StringJoinerklasa.
theTechnoKid
138

Nie po wyjęciu z pudełka, ale wiele bibliotek ma podobne:

Commons Lang:

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

Wiosna:

org.springframework.util.StringUtils.collectionToDelimitedString(list, conjunction);
Arne Burmeister
źródło
125

Na Androidzie możesz użyć klasy TextUtils .

TextUtils.join(" and ", names);
Rafał Enden
źródło
11
Szukałem tego. String.joinwymaga minimum 26 interfejsu API na Androida.
okarakose
49

Nie, nie ma takiej wygodnej metody w standardowym API Java.

Nic dziwnego, że Apache Commons zapewnia coś takiego w swojej klasie StringUtils na wypadek, gdybyś nie chciał sam pisać.

Bart Kiers
źródło
11
Zawsze denerwowało mnie, że String ma podział, ale nie połączenie. W pewnym sensie ma to sens, ale jest po prostu denerwujące, że nie ma co najmniej statycznej metody w String.
Władca
3
Jestem z tobą Bemrose. Przynajmniej dali nam isEmpty()metodę zamiast (statycznej) join()metody w String...: rollseyes: :)
Bart Kiers
41

Trzy możliwości w Javie 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("");
amra
źródło
27

W przypadku kolektora Java 8 można to zrobić za pomocą następującego kodu:

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

Również najprostsze rozwiązanie w java 8:

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

lub

String.join(" and ", Arrays.asList("Bill", "Bob", "Steve"));
aifa
źródło
21

Napisałem ten (używam go do fasoli i wykorzystuję toString, więc nie pisz 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();
}

ale Collectionnie jest obsługiwany przez JSP, więc dla TLD napisałem:

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

i do .tldakt:

<?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>

i użyj go w plikach JSP jako:

<%@taglib prefix="funnyFmt" uri="tag:com.core.util,2013:funnyFmt"%>
${funnyFmt:join(books, ", ")}
gavenkoa
źródło
1
zaproponować zmianę Collection<>na Iterable<>?
Jason S
@JasonS +1. Dobra uwaga, ale przeczytaj stackoverflow.com/questions/1159797/…
gavenkoa
19

Kod, który posiadasz jest właściwym sposobem, aby to zrobić, jeśli chcesz korzystać z JDK bez żadnych zewnętrznych bibliotek. W JDK nie ma prostej „jednej linii”, której można by użyć.

Jeśli możesz korzystać z zewnętrznych bibliotek, zalecamy zajrzenie do org.apache.commons.lang.StringUtils klasy w bibliotece Apache Commons.

Przykład użycia:

List<String> list = Arrays.asList("Bill", "Bob", "Steve");
String joinedResult = StringUtils.join(list, " and ");
Juha Syrjälä
źródło
17

Ortodoksyjnym sposobem na osiągnięcie tego jest zdefiniowanie nowej funkcji:

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

Próba:

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

Wynik:

7, 7, 7 oraz Bill i Bob, Steve i [Bill] oraz 1,2,3 i Apple] [i ~, ~

Daniel De León
źródło
5
nadanie parametrowi metody takiej samej nazwy jak sama metoda prawdopodobnie nie jest najlepszym pomysłem. separatorbyłby o wiele bardziej sugestywny.
ccpizza,
10

Rozwiązanie Java 8 z java.util.StringJoiner

Java 8 ma StringJoinerklasę. Ale nadal musisz napisać trochę bojlera, ponieważ jest to Java.

StringJoiner sj = new StringJoiner(" and ", "" , "");
String[] names = {"Bill", "Bob", "Steve"};
for (String name : names) {
   sj.add(name);
}
System.out.println(sj);
klingt.net
źródło
Nie musisz pisać trochę bojlera, jeśli używasz wygodniejszej metody String.join () .
Andy Thomas,
9

Możesz użyć biblioteki apache commons, która ma klasę StringUtils i metodę łączenia.

Sprawdź ten link: https://commons.apache.org/proper/commons-lang/javadocs/api.2.0/org/apache/commons/lang/StringUtils.html

Zauważ, że powyższy link może z czasem stać się nieaktualny, w takim przypadku możesz po prostu wyszukać w sieci hasło „apache commons StringUtils”, które powinno umożliwić znalezienie najnowszej referencji.

(przywołane w tym wątku) Ekwiwalenty Java C # String.Format () i String.Join ()

dcp
źródło
7

Możesz to zrobić:

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

Lub liniowiec (tylko wtedy, gdy nie chcesz „[” i „]”)

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

Mam nadzieję że to pomoże.

NawaMan
źródło
1
Nie doda to koniunkcji wyboru.
hexium
OOPS !! Przepraszam, nie zauważyłem tego.
NawaMan,
6

Świetny sposób na zrobienie tego za pomocą czystego JDK w jednym wierszu:

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

Wynik:

Bill i Bob, Steve i [Bill] oraz 1,2,3 i Apple] [


Niezbyt doskonały i niezbyt zabawny sposób!

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

Wynik:

7, 7, 7 oraz Bill i Bob, Steve i [Bill] oraz 1,2,3 i Apple] [

Daniel De León
źródło
Wypróbuj z „[Bill]”, „1,2,3” i „Apple] [”. Kreatywny, ale ma przypadki, w których jest niepoprawny.
Jason S
1
Teraz spróbuj z „~, ~”. Nie można stworzyć programu z tego rodzaju architekturą całkowicie kuloodporną.
Jason S
Tak, wiem ... Usunąłem głosowanie. Ale powinieneś pomyśleć nieco bardziej ostrożnie o zamieszczaniu odpowiedzi tutaj, szczególnie w przypadku pytań, które mają kilka lat. Odpowiedzi są nie tylko natychmiast przydatne w przypadku oryginalnego plakatu, ale pojawiają się także w wynikach wyszukiwania Google. (W rzeczywistości w przypadku pytań, które mają kilka lat, nowe odpowiedzi mogą nie mieć żadnej wartości dla oryginalnego plakatu.) Rozwiązania, które mają wady lub błędy, mogą być szkodliwe dla osób, które znajdą je później poza kontekstem.
Jason S
1
Dużo się tutaj uczę od innych postów, takich jak mój, z nienajlepszym rozwiązaniem, ale naprawdę bogatym w wiedzę i myślenie poboczne. Zrelaksuj się dla innych, każdy musi zrozumieć, co robi każdy wiersz kodu. Pamiętaj, że programowanie jest jak sztuka, a nawet każdy wiersz kodu oznacza coś o osobowości programisty. I tak, nie będę używać tego kodu w produkcji!
Daniel De León
4

Jeśli korzystasz z kolekcji Eclipse (wcześniej GS Collection ), możesz użyć tej makeString()metody.

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

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

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

Jeśli możesz przekonwertować swój Listtyp na kolekcje Eclipse, możesz pozbyć się adaptera.

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

Jeśli chcesz tylko łańcuch rozdzielany przecinkami, możesz użyć wersji, makeString()która nie przyjmuje parametrów.

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

Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.

Craig P. Motlin
źródło
3

W java 1.8 strumień może być używany,

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 "));
Saurabh
źródło
3

EDYTOWAĆ

Zauważam również toString() podstawowy problem z implementacją i element zawierający separator, ale myślałem, że jestem paranoikiem.

Ponieważ mam dwa komentarze na ten temat, zmieniam odpowiedź na:

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

Który wygląda dość podobnie do pierwotnego pytania.

Jeśli więc nie chcesz dodawać całego słoika do swojego projektu, możesz go użyć.

Myślę, że nie ma nic złego w twoim oryginalnym kodzie. W rzeczywistości alternatywa, którą sugerują wszyscy, wygląda prawie tak samo (chociaż wykonuje szereg dodatkowych weryfikacji)

Oto on, wraz z licencją 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();
}

Teraz wiemy, dziękuję open source

OscarRyz
źródło
To zadziała teraz, ale nie możesz być pewien, jak zachowa się List.toString () w przyszłości. Nigdy nie ufałbym operacji toString () innej niż wydrukowanie jakiegoś ciągu informacyjnego o danym obiekcie. W swojej obronie nie jesteś jedynym, który proponuje to rozwiązanie.
Hans Doggen
Co jeśli zawartość elementu na liście zawiera podciąg ", "?
Bart Kiers,
@Hans & @Bart: Masz rację. Myślałem, że nikt inny nie zauważy: P Zmieniam odpowiedź.
OscarRyz
To zależy od przypadku użycia. Do celów debugowania toString () jest w porządku IMO ... pod warunkiem, że nie polegasz na określonym formatowaniu. Mimo to, jeśli napiszesz testy kodu, wszystkie przyszłe zmiany zostaną przechwycone.
akostadinov
2

Interfejs API Google Guava ma również .join (), chociaż (jak powinno być oczywiste w przypadku innych odpowiedzi), Apache Commons jest tutaj standardem.

Dziekan J.
źródło
1

Java 8 przynosi

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

Metoda, która jest nullsafe przy użyciu prefix + suffixwartości null.

Można go użyć w następujący sposób:

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

Collectors.joining(CharSequence delimiter)Metoda po prostu wywołuje joining(delimiter, "", "")wewnętrznie.

gr
źródło
0

Możesz użyć tego z StringUtils Spring Framework. Wiem, że już o tym wspomniano, ale tak naprawdę możesz po prostu wziąć ten kod i działa natychmiast, bez potrzeby Springa.

// 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();
    }
}
EpicPandaForce
źródło
-3

Spróbuj tego:

java.util.Arrays.toString(anArray).replaceAll(", ", ",")
                .replaceFirst("^\\[","").replaceFirst("\\]$","");
Jorge
źródło
1
Prawdopodobnie dlatego, że pytanie wymaga użycia listy zamiast tablicy? wzruszenie ramionami
Nick Coelius,
4
Ponieważ jeśli masz w ciągach „,”, to nie zadziała.
Cyrille Pontvieux