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.


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.


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.


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

125

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

TextUtils.join(" and ", names);

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ć.


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

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

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, ", ")}

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

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 ~, ~


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

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


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.


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] [


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.


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

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


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.


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.


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

-3

Spróbuj tego:

java.util.Arrays.toString(anArray).replaceAll(", ", ",")
                .replaceFirst("^\\[","").replaceFirst("\\]$","");

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.