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.