使用流将对象列表转换为从toString方法获取的字符串

279

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包含整数123,我希望concatenated"123""1,2,3"


可能是Java:将List <String>转换为String的重复问题。 - Vadzim
15个回答

498

一个简单的方法是在 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);

解释:map方法将一个整数流映射为一个字符串流,然后将所有元素连接起来,实现了简单的降维操作。

注意:这是一个普通的降维操作,时间复杂度为O(n2)。为了获得更好的性能,请使用StringBuilder或类似于F. Böller所提供的可变降维操作。

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

参考:Java Stream Reduction


2
是的,这不是一种内联解决方案,但它是一种解决方案。 - mat_boy
2
我不明白为什么“正常的归约在O(n2)内执行”。对我来说,它更像是O(n)... - datahaki
3
我不熟悉Java,但我猜迭代字符串连接需要在每次迭代时对数组进行重新分配,这使得它的时间复杂度为O(n^2)。另一方面,join会预先分配一个足够大的单个数组来存储最终字符串,然后再填充它,使其时间复杂度为O(n)。 - Eli Korvigo
2
非常好的提示。也许你可以使用“::”符号简化代码。因此,list.stream().map(Object::toString).reduce("", String::concat)。使用map e-> e.toString()有点多余。 - e2a
String s = list.stream().map(e -> e.toString()).reduce("", String::concat); 这种写法会导致性能损耗为 O(n^2)(糟糕)。我更喜欢 String s = list.stream().map(Object::toString).collect(Collectors.joining(",")); 这种写法,它的性能为 O(n)。请参考 https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html,在页面中搜索 However, we might not be happy about the performance! Such an implementation would do a great deal of string copying, and the run time would be O(n^2) in the number of characters - undefined
显示剩余2条评论

243

API 中有一个名为 joining 的集合器。 它是 Collectors 类中的一个静态方法。

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

虽然需要调用toString,不过并不完美,但是可以工作。还有其他可能的分隔符。


8
为了完整性:要使该代码正常工作,您需要 import static java.util.stream.Collectors.joining; - Mahdi
8
为什么我们需要映射和显式的`toString'?如果收集器期望字符串元素,则转换应该隐式调用,不是吗?!为什么需要映射和显式的`toString'?如果收集器期望的是字符串元素,那么转换应该是隐式发生的,对吗?! - Basel Shishani
1
@BaselShishani 不行。收集器需要“CharSequence”对象,因此需要进行转换。您需要一个接受对象并自行执行“toString()”操作的收集器,以便跳过“map(Object::toString)”。 - Datz

11

如果有人想在没有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分隔元素;如果您的数据中有方括号,请使用substring版本,但对于数字数组来说,它几乎是完美的。


虽然这通常可以工作,但不能保证——List不强制执行toString()的结构。然而,AbstractCollection默认使用此结构,我认为Java SE中所有通用的List实现也是如此。例如,Spring中的org.springframework.util.AutoPopulatingList就是一个不实现toString()的例子,因此会返回例如"org.springframework.util.AutoPopulatingList@170a1"。 - M. Justin
Arrays.toString()Arrays.asList(list).toString()更好,因为它被定义为返回一个等效的字符串,更加简洁,并且不需要额外的对象创建。 - M. Justin
@M.Justin 对于这个答案中的数组部分,你说得不错,但是对于一个普通的“List”呢?你不能真的在上面使用Arrays.toString()。你会建议如何在像你提到的AutoPopulatingList这样的东西上使用这种技术呢?也许:new ArrayList(autoPopulatingList).toString()?或者我猜你可以将其转换为数组。也许如果你遇到这样的问题,你应该希望你在1.8或更高版本上,并且可以使用本主题中的其他答案之一... - Bill K

10

在 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

6
其他答案都不错。不过,你也可以将Collectors.toList()作为参数传递给Stream.collect(),以将元素作为ArrayList返回。
System.out.println( list.stream().map( e -> e.toString() ).collect( toList() ) );

如果你使用System.out.print(ln)(list),它将使用元素的toString()方法来打印该列表。所以这段代码只是重复了ListtoString内部的操作。 - Shirkam
1
应该使用Collectors.toList(),除非你正在导入静态方法。 - mwieczorek
是的...我为了这个例子导入了静态变量。我在答案主体中声明了“Collectors.toList()”... ;) - Edward J Beckett

4

将对象列表转换为字符串列表:

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


2
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

尝试了Shail016bpedroso的两种方法(https://dev59.com/82Af5IYBdhLWcg3wOQi2#24883180),在for循环内使用简单的StringBuilder+append(String)似乎比list.stream().map([...]执行得更快。

例如:此代码遍历一个Map<Long,List<Long>>,使用list.stream().map([...]构建json字符串:

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

在我的开发虚拟机上,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());
}

1

一种干净的方法是将列表的元素映射为字符串,然后使用Collectors类中的joining操作。

List<Integer> ls = new ArrayList<Integer>();
ls.add(1);
ls.add(2);
ls.add(3);
String s = ls.stream().map(Object::toString).collect(Collectors.joining(","));

重复的答案 https://dev59.com/82Af5IYBdhLWcg3wOQi2#24884212 - I Stevenson

1

使用流

    List<Integer> list = Arrays.asList(1, 2, 3);
    String s = list.stream().map((i)->i.toString()).collect(Collectors.joining());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接