Использование Java 8 для преобразования списка объектов в строку, полученную из метода toString ()


206

В Java 8 появилось много полезных новых вещей. Например, я могу перебирать поток по списку объектов, а затем суммировать значения из определенного поля Objectэкземпляров. Например

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

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

Таким образом, я спрашиваю, существует ли какой-либо способ для создания, Stringкоторый объединяет выходные данные toString()метода из экземпляров в одной строке.

List<Integer> list = ...

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

Предположим, что listсодержит целые числа 1, 2и 3, я ожидаю, что concatenatedэто "123"или "1,2,3".


Ответы:


369

Один простой способ - добавить элементы списка в 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);

Вы также можете попробовать:

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

Объяснение: карта преобразует поток Integer в поток String, а затем уменьшается как объединение всех элементов.

Примечание: это то, normal reductionчто выполняет в O (n 2 )

для лучшей производительности используйте ответ StringBuilderили mutable reductionаналогичен ответу Ф. Беллера.

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

Ссылка: сокращение потока


2
Да, не встроенное решение, а решение.
mat_boy

не понял этого Что вы ожидаете?
Shail016

2
я не понимаю, почему "нормальное сокращение, которое выполняет в O (n2)". для меня это "выглядит" больше как O (n) ...
datahaki

2
@datahaki Я не Java-парень, но я предполагаю, что итеративная конкатенация (сокращение) строк требует перераспределения массива на каждой итерации, что делает его O (n ^ 2). joinс другой стороны, будет предварительно выделен один массив, достаточно большой, чтобы сохранить итоговую строку перед ее заполнением, что делает его O (n).
Эли Корвиго

2
Очень хороший совет. Может быть, вы можете упростить использование обозначения "::". Так, list.stream().map(Object::toString).reduce("", String::concat). Использование map e-> e.toString()немного избыточно.
e2a

194

В joiningAPI есть коллектор . Это статический метод в Collectors.

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

Не идеально из-за необходимого вызова toString, но работает. Возможны разные разделители.


7
Для полноты: чтобы заставить этот точный код работать, вам нужноimport static java.util.stream.Collectors.joining;
Махди

7
Зачем нам нужно отображение и явное `toString '? Если сборщик ожидает элементы String, тогда преобразование должно вызываться неявно, нет ?!
Базель Шишани

10

На всякий случай, если кто-то пытается сделать это без Java 8, есть довольно хороший трюк. List.toString () уже возвращает коллекцию, которая выглядит следующим образом:

[1,2,3]

В зависимости от ваших конкретных требований, это может быть постобработано до того, что вы хотите, если ваши элементы списка не содержат [] или,.

Например:

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

или если ваши данные могут содержать квадратные скобки это:

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

даст вам довольно разумный вывод.

Один элемент массива в каждой строке может быть создан следующим образом:

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

Я использовал эту технику для создания всплывающих подсказок HTML из списка в небольшом приложении, с чем-то вроде:

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

Если у вас есть массив, тогда начните с Arrays.asList (list) .toString ()

Я полностью осознаю тот факт, что это не оптимально, но не так неэффективно, как вы думаете, и довольно просто читать и понимать. Это, однако, довольно негибко - в частности, не пытайтесь разделить элементы с replaceAll, если ваши данные могут содержать запятые, и используйте версию подстроки, если у вас есть квадратные скобки в ваших данных, но для массива чисел это в значительной степени идеальный.


Хотя обычно это работает, это не гарантирует - Listне обеспечивает структуру toString(). AbstractCollectionоднако, эта структура по умолчанию используется, и я думаю, что все реализации общего назначения Listв Java SE делают то же самое. В качестве примера того, который этого не делает, org.springframework.util.AutoPopulatingListв Spring не реализуется toString()и поэтому возвращает, например, " org.springframework.util.AutoPopulatingList@170a1".
М. Джастин

Arrays.toString()было бы лучшим выбором, чем Arrays.asList(list).toString(), поскольку он определен для возврата эквивалентной строки, он более лаконичен и не требует создания дополнительного объекта.
М. Джастин

@ M.Justin Неплохая точка для части массива этого ответа, но как насчет простого «Списка»? Вы не можете использовать Arrays.toString () для этого. Как бы вы предложили использовать этот метод для чего-то вроде упомянутого вами списка AutoPopulationList? Может быть: новый ArrayList (autoPopulationList) .toString ()? Или я думаю, вы могли бы преобразовать его в массив. Может быть, если вы столкнулись с такой проблемой, вы должны надеяться, что вы используете 1.8 или более позднюю версию и сможете использовать один из других ответов в этой теме ...
Билл К,

5

Другие ответы в порядке. Однако вы также можете передать Collectors.toList () в качестве параметра в Stream.collect (), чтобы вернуть элементы в виде ArrayList.

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

Если вы используете System.out.print(ln), (list)он будет использовать toString()метод элементов для печати списка. Итак, этот кусок кода просто повторяет то, что происходит внутри toStringList.
Ширкам

1
Должно быть Collectors.toList (), если вы не импортируете static.
mwieczorek

Да ... Я импортировал статические для примера. Я сказал «Collectors.toList ()» в теле ответа ...;)
Эдди Б


2

В String API есть метод для тех «сценариев присоединения к списку строк», вам даже не нужен 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);

Или

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

Этот подход позволяет также построить строковый результат из списка объектов. Пример

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

Здесь функция Reduce позволяет вам иметь некоторое начальное значение, к которому вы хотите добавить новую строку. Пример:

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

1

Тестирование обоих подходов, предложенных в Shail016 и ответе bpedroso ( https://stackoverflow.com/a/24883180/2832140 ), простой StringBuilder+ append(String)внутри forцикла, кажется, выполняется намного быстрее, чемlist.stream().map([...] .

Пример: этот код проходит через Map<Long, List<Long>>строку json, используя 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());
}

На моей виртуальной машине dev для выполнения теста junit обычно занимает от 0,35 до 1,2 секунды. В то время как, используя этот следующий код, это занимает от 0,15 до 0,33 секунды:

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

Можем ли мы попробовать это

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
Обычно хорошей идеей будет добавить в ваш пост объяснение, в котором рассказывается о том, как работает код. Это позволяет новым разработчикам понять, что делает код.
Калеб Клеветер

1
@CalebKleveter сокращает Listдо одного Stringи разделяет каждый элемент запятой ( ,).
Джо Алмор

2
Ваше решение работает ТОЛЬКО, если list является List <String>. ОП не указывал такой класс. В его вопросе даже указан List <Integer>. Ваш код не компилируется в этом случае.
RubioRic

0

Я собираюсь использовать API потоков для преобразования потока целых чисел в одну строку. Проблема с некоторыми из предоставленных ответов заключается в том, что они генерируют O (n ^ 2) во время выполнения из-за построения String. Лучшее решение - использовать StringBuilder, а затем объединить строки в качестве последнего шага.

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

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

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

С Java 8+

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

Не самое эффективное, но это решение с небольшим количеством кода.


-2

Кроме того, вы можете сделать это.

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

Ваше решение работает ТОЛЬКО, если list является List <String>. ОП не указывал такой класс. В его вопросе даже указан List <Integer>.
RubioRic
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.