Korzystanie z Java 8 do konwersji listy obiektów na ciąg uzyskany z metody toString ()


206

W Javie jest wiele przydatnych nowych rzeczy. Np. Mogę iterować ze strumieniem nad listą obiektów, a następnie sumować wartości z określonego pola Objectinstancji. Na przykład

public class AClass {
  private int value;
  public int getValue() { return value; }
}

Integer sum = list.stream().mapToInt(AClass::getValue).sum();

Dlatego pytam, czy jest jakiś sposób na zbudowanie, Stringktóry konkatenuje wynik toString()metody z instancji w jednym wierszu.

List<Integer> list = ...

String concatenated = list.stream().... //concatenate here with toString() method from java.lang.Integer class

Załóżmy, że listzawiera liczby całkowite 1, 2i 3oczekuję, że concatenatedjest "123"albo "1,2,3".


Odpowiedzi:


369

Jednym prostym sposobem jest dołączenie elementów listy do StringBuilder

   List<Integer> list = new ArrayList<>();
   list.add(1);
   list.add(2);
   list.add(3);

   StringBuilder b = new StringBuilder();
   list.forEach(b::append);

   System.out.println(b);

możesz także spróbować:

String s = list.stream().map(e -> e.toString()).reduce("", String::concat);

Objaśnienie: mapa konwertuje strumień liczb całkowitych na strumień ciągów, a następnie jest redukowany jako konkatenacja wszystkich elementów.

Uwaga: To normal reductiondziała w O (n 2 )

dla lepszej wydajności użyj StringBuilderlub mutable reductionpodobny do odpowiedzi F. Böllera.

String s = list.stream().map(Object::toString).collect(Collectors.joining(","));

Ref: Redukcja strumienia


2
Tak, nie rozwiązanie wbudowane, ale rozwiązanie.
mat_boy

nie rozumiem Czego się spodziewasz
Shail016,

2
nie rozumiem, dlaczego „normalna redukcja, która występuje w O (n2)”. dla mnie „wygląda” bardziej jak O (n) ...
datahaki,

2
@datahaki Nie jestem facetem w Javie, ale zgaduję, że iteracyjne łączenie łańcuchów (redukcja) wymaga alokacji (re) tablic przy każdej iteracji, co czyni ją O (n ^ 2). joinz drugiej strony wstępnie przydzieliłby pojedynczą tablicę wystarczająco dużą, aby zapisać ostatni łańcuch przed zapełnieniem go, czyniąc go O (n).
Eli Korvigo,

2
Bardzo dobra wskazówka. Może możesz uprościć, używając notacji „::”. Więc list.stream().map(Object::toString).reduce("", String::concat). Korzystanie map e-> e.toString()jest trochę zbędne.
e2a

194

joiningInterfejs API zawiera moduł zbierający . Jest to metoda statyczna w Collectors.

list.stream().map(Object::toString).collect(Collectors.joining(","))

Nie idealny z powodu koniecznego wezwania toString, ale działa. Możliwe są różne ograniczniki.


7
Dla kompletności: aby ten dokładny kod zadziałał, musiszimport static java.util.stream.Collectors.joining;
Mahdi

7
Dlaczego potrzebujemy mapowania i wyraźnego „toString”? Jeśli kolektor oczekuje elementów String, konwersję należy wywołać niejawnie, nie ?!
Basel Shishani,

10

Na wypadek, gdyby ktoś próbował to zrobić bez java 8, istnieje całkiem niezła sztuczka. List.toString () już zwraca kolekcję, która wygląda następująco:

[1,2,3]

W zależności od konkretnych wymagań można to przetworzyć dowolnie, o ile elementy listy nie zawierają [] lub,.

Na przykład:

list.toString().replace("[","").replace("]","") 

lub jeśli twoje dane mogą zawierać nawiasy kwadratowe:

String s=list.toString();
s = s.substring(1,s.length()-1) 

da ci całkiem rozsądną wydajność.

W każdym wierszu można utworzyć jeden element tablicy w następujący sposób:

list.toString().replace("[","").replace("]","").replaceAll(",","\r\n")

Użyłem tej techniki do tworzenia podpowiedzi HTML z listy w małej aplikacji, z czymś takim jak:

list.toString().replace("[","<html>").replace("]","</html>").replaceAll(",","<br>")

Jeśli masz tablicę, zacznij od Arrays.asList (list) .toString ()

Całkowicie posiadam fakt, że nie jest to optymalne, ale nie jest tak nieefektywne, jak mogłoby się wydawać, i jest łatwe do odczytania i zrozumienia. Jest jednak dość nieelastyczny - w szczególności nie próbuj oddzielać elementów poleceniem replaceAll, jeśli dane mogą zawierać przecinki, i użyj wersji podłańcuchowej, jeśli masz w nawiasach kwadratowych dane, ale dla tablicy liczb jest to w zasadzie idealny.


Chociaż zwykle będzie to działać, nie ma gwarancji, że nie - Listnie wymusza struktury toString(). AbstractCollectionJednak domyślnie korzysta z tej struktury i myślę, że również wszystkie Listimplementacje ogólnego przeznaczenia w Java SE. Jako przykład takiego, który tego nie robi, org.springframework.util.AutoPopulatingListna wiosnę nie implementuje toString()i dlatego zwróci np. „ org.springframework.util.AutoPopulatingList@170a1”.
M. Justin

Arrays.toString()byłby lepszym wyborem niż Arrays.asList(list).toString(), ponieważ zdefiniowano, aby zwracał równoważny ciąg, jest bardziej zwięzły i nie wymaga dodatkowego tworzenia obiektu.
M. Justin

@ M.Justin Nie jest to zły punkt dla części tablicowej tej odpowiedzi, ale co z prostą „Listą”? Nie można na tym naprawdę używać Arrays.toString (). Jak sugerowałbyś użycie tej techniki na czymś takim, jak wspomniana lista AutoPopulationList? Może: new ArrayList (autoPopulationsList) .toString ()? Albo myślę, że możesz przekonwertować go na tablicę. Może jeśli napotkasz taki problem, powinieneś mieć nadzieję, że masz 1.8 lub nowszą wersję i mógłbyś użyć jednej z innych odpowiedzi w tym wątku ...
Bill K

5

Inne odpowiedzi są w porządku. Można jednak również przekazać Collectors.toList () jako parametr do Stream.collect (), aby zwrócić elementy jako ArrayList.

System.out.println( list.stream().map( e -> e.toString() ).collect( toList() ) );

Jeśli użyjesz System.out.print(ln) (list), użyje toString()metody elementów do wydrukowania listy. Zatem ten fragment kodu po prostu powtarza to, co dzieje się wewnątrz toStringListy.
Shirkam

1
Powinien być Collectors.toList (), chyba że importujesz statyczny.
mwieczorek

Tak ... Zaimportowałem statyczny dla przykładu. Powiedziałem: „Collectors.toList ()” w treści odpowiedzi ...;)
Eddie B,

2

StringListName = ObjectListName.stream (). Map (m -> m.toString ()) .collect (Collectors.toList ());


2

W interfejsie API String istnieje metoda dla przypadków użycia „dołączania listy ciągów”, nawet nie potrzebujesz Stream.

List<String> myStringIterable = Arrays.asList("baguette", "bonjour");

String myReducedString = String.join(",", myStringIterable);

// And here you obtain "baguette,bonjour" in your myReducedString variable

1
List<String> list = Arrays.asList("One", "Two", "Three");
    list.stream()
            .reduce("", org.apache.commons.lang3.StringUtils::join);

Lub

List<String> list = Arrays.asList("One", "Two", "Three");
        list.stream()
                .reduce("", (s1,s2)->s1+s2);

Podejście to pozwala również zbudować ciąg znaków z listy obiektów Przykład

List<Wrapper> list = Arrays.asList(w1, w2, w2);
        list.stream()
                .map(w->w.getStringValue)
                .reduce("", org.apache.commons.lang3.StringUtils::join);

Tutaj funkcja zmniejszania pozwala uzyskać pewną wartość początkową, do której chcesz dołączyć nowy ciąg Przykład:

 List<String> errors = Arrays.asList("er1", "er2", "er3");
            list.stream()
                    .reduce("Found next errors:", (s1,s2)->s1+s2);

1

Testowanie obu podejść sugerowanych w Shail016 i odpowiedzi bpedroso ( https://stackoverflow.com/a/24883180/2832140 ), prosta StringBuilder+ append(String)w forpętli, wydaje się działać znacznie szybciej niż list.stream().map([...].

Przykład: ten kod przechodzi przez Map<Long, List<Long>>kompilację łańcucha json, używając list.stream().map([...]:

if (mapSize > 0) {
    StringBuilder sb = new StringBuilder("[");

    for (Map.Entry<Long, List<Long>> entry : threadsMap.entrySet()) {

        sb.append("{\"" + entry.getKey().toString() + "\":[");
        sb.append(entry.getValue().stream().map(Object::toString).collect(Collectors.joining(",")));
    }
    sb.delete(sb.length()-2, sb.length());
    sb.append("]");
    System.out.println(sb.toString());
}

Na mojej maszynie deweloperskiej junit zwykle wykonuje od 0,35 do 1,2 sekundy. Podczas korzystania z tego kodu zajmuje od 0,15 do 0,33 sekundy:

if (mapSize > 0) {
    StringBuilder sb = new StringBuilder("[");

    for (Map.Entry<Long, List<Long>> entry : threadsMap.entrySet()) {

        sb.append("{\"" + entry.getKey().toString() + "\":[");

        for (Long tid : entry.getValue()) {
            sb.append(tid.toString() + ", ");
        }
        sb.delete(sb.length()-2, sb.length());
        sb.append("]}, ");
    }
    sb.delete(sb.length()-2, sb.length());
    sb.append("]");
    System.out.println(sb.toString());
}

0

Czy możemy tego spróbować?

public static void main(String []args){
        List<String> stringList = new ArrayList<>();
        for(int i=0;i< 10;i++){
            stringList.add(""+i);
        }
        String stringConcated = String.join(",", stringList);
        System.out.println(stringConcated);

    }

0
String actual = list.stream().reduce((t, u) -> t + "," + u).get();

7
Zwykle dobrym pomysłem jest dodanie wyjaśnienia do posta, które mówi o tym, jak działa kod. Dzięki temu nowi programiści mogą zrozumieć, co robi kod.
Caleb Kleveter

1
@CalebKleveter zmniejsza Listdo jednego Stringi oddziela każdy element przecinkiem ( ,).
Joe Almore,

2
Twoje rozwiązanie działa TYLKO, jeśli lista jest Listą <String>. OP nie określił takiej klasy. W jego pytaniu wskazana jest nawet lista <Liczba całkowita>. W takim przypadku Twój kod się nie kompiluje.
RubioRic,

0

Zamierzam użyć interfejsu API strumieni do konwersji strumienia liczb całkowitych na pojedynczy ciąg. Problem z niektórymi z podanych odpowiedzi polega na tym, że wytwarzają one środowisko wykonawcze O (n ^ 2) z powodu budowania ciągów. Lepszym rozwiązaniem jest użycie StringBuilder, a następnie połączenie ciągów razem jako ostatni krok.

//              Create a stream of integers 
    String result = Arrays.stream(new int[]{1,2,3,4,5,6 })                
            // collect into a single StringBuilder
            .collect(StringBuilder::new, // supplier function
                    // accumulator - converts cur integer into a string and appends it to the string builder
                    (builder, cur) -> builder.append(Integer.toString(cur)),
                    // combiner - combines two string builders if running in parallel
                    StringBuilder::append) 
            // convert StringBuilder into a single string
            .toString();

Możesz pójść o krok dalej, konwertując kolekcję obiektów na pojedynczy ciąg.

// Start with a class definition
public static class AClass {
    private int value;
    public int getValue() { return value; }
    public AClass(int value) { this.value = value; }

    @Override
    public String toString() {
        return Integer.toString(value);
    }
}

// Create a stream of AClass objects
        String resultTwo = Arrays.stream(new AClass[]{
                new AClass(1),
                new AClass(2),
                new AClass(3),
                new AClass(4)
        })
                // transform stream of objects into a single string
                .collect(StringBuilder::new,
                        (builder, curObj) -> builder.append(curObj.toString()),
                        StringBuilder::append
                )
            // finally transform string builder into a single string
            .toString();

-1

Z Javą 8+

String s = Arrays.toString(list.stream().toArray(AClass[]::new));

Nie jest najbardziej wydajny, ale jest to rozwiązanie z niewielką ilością kodu.


-2

Możesz to zrobić w ten sposób.

    List<String> list = Arrays.asList("One", "Two", "Three");
    String result = String.join(", ", list);
    System.out.println(result);

Twoje rozwiązanie działa TYLKO, jeśli lista jest Listą <String>. OP nie określił takiej klasy. W jego pytaniu wskazana jest nawet lista <Liczba całkowita>.
RubioRic,
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.