将ArrayList转换为字符串的最佳方法

352

我有一个 ArrayList,我想将其完整地输出为一个字符串。基本上,我想使用每个元素的toString按顺序分隔以制表符的方式来输出它。有没有一种快速的方法可以做到这一点?你可以循环遍历它(或者移除每个元素)并将其连接到一个字符串中,但我认为这将非常慢。


4
注意,从ArrayList列表中删除元素是不可取的,因为您的“循环”会由于移动元素而花费二次时间(前提是您从第一个到最后一个元素进行循环)。请注意,删除元素的操作可能会对性能产生重大影响。 - Dimitry K
同样适用于集合:https://dev59.com/1XRC5IYBdhLWcg3wJNmf - Ciro Santilli OurBigBook.com
27个回答

1193

在Java 8及以后版本中:

String listString = String.join(", ", list);

如果列表不是字符串类型,则可以使用连接收集器:

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

194
我无法相信直到Java 8才添加这个功能。 - Paul
58
这个答案应该得到更多的赞!没有黑科技,没有库和循环。 - Songo
5
这对OP所说的情况不起作用。String.join的第二个参数需要传入Iterable<? extends CharSequence>。因此,如果你有一个要显示的List<String>,它就可以工作,但如果你有List<MyObject>,它将不会像OP想要的那样调用其中的toString()方法。 - Hilikus
3
为什么这不是第一个(且被接受的)答案。每次我都要往下滚动才知道它在哪里。简洁明了,优雅明了。 - Jack
3
要求使用 Android API 26 或更高版本。 - Arsen Sench
显示剩余9条评论

426

如果你正在使用Android,则有一个称为TextUtils的实用工具,它有一个.join(String delimiter, Iterable)方法可以做到这一点。

List<String> list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
String joined = TextUtils.join(", ", list);

显然这个功能只适用于Android,但我觉得把它添加到这个帖子中也挺好的...


1
由于 TextUtils.join 接受一个 Object[],我认为它会在每个标记上调用 toString()PatientDetails 是否实现了 toString()?如果这不能解决问题,你可能需要使用 Separator 类来处理。 - JJ Geewax
6
org.apache.lang3.StringUtils 几乎相同,因此你的答案对我非常有用,不仅限于 Android :) - avalancha
2
这个答案的受欢迎程度(目前是最高票的)表明许多Java问题源于Android开发。 - Amr H. Abd Elmajeed
2
我可以打赌你被派到这个地球上来解决我的问题 :) - kofoworola
2
你救了我的命。 <3 - Pratik Butani
显示剩余4条评论

382
Java 8引入了一个String.join(separator, list)方法; 参见Vitalii Federenko的答案
在Java 8之前,使用循环迭代ArrayList是唯一的选择: 请勿使用此代码,请继续阅读本答案底部以了解为什么不建议使用它,以及应该使用哪些代码:
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

String listString = "";

for (String s : list)
{
    listString += s + "\t";
}

System.out.println(listString);

事实上,字符串拼接是完全可以的,因为javac编译器会将字符串拼接优化为一系列StringBuilder上的append操作。以下是上述程序中for循环的字节码反汇编的一部分:
   61:  new #13; //class java/lang/StringBuilder
   64:  dup
   65:  invokespecial   #14; //Method java/lang/StringBuilder."<init>":()V
   68:  aload_2
   69:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  aload   4
   74:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   77:  ldc #16; //String \t
   79:  invokevirtual   #15; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   82:  invokevirtual   #17; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

可以看出,编译器通过使用StringBuilder对循环进行了优化,因此性能不应该是一个大问题。(好吧,仔细一看,StringBuilder在每次循环迭代时都被实例化,所以它可能不是最有效的字节码。手动实例化并使用显式的StringBuilder可能会产生更好的性能。)事实上,我认为任何输出(无论是到磁盘还是屏幕)都将比担心字符串连接的性能慢至少一个数量级。 编辑:正如评论中指出的那样,上述编译器优化确实在每次迭代中创建了StringBuilder的新实例。(我之前已经注意到了这一点。)最优化的技术将使用Paul Tomblin的响应,因为它只在for循环之外实例化单个StringBuilder对象。
将上述代码重写为:
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");

StringBuilder sb = new StringBuilder();
for (String s : list)
{
    sb.append(s);
    sb.append("\t");
}

System.out.println(sb.toString());

为了提高代码效率,循环外只需要实例化StringBuilder一次,在循环内部调用两次append方法。下面的字节码显示了StringBuilder的实例化和循环:

   // Instantiation of the StringBuilder outside loop:
   33:  new #8; //class java/lang/StringBuilder
   36:  dup
   37:  invokespecial   #9; //Method java/lang/StringBuilder."<init>":()V
   40:  astore_2

   // [snip a few lines for initializing the loop]
   // Loading the StringBuilder inside the loop, then append:
   66:  aload_2
   67:  aload   4
   69:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   72:  pop
   73:  aload_2
   74:  ldc #15; //String \t
   76:  invokevirtual   #14; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   79:  pop

因此,手动优化应该表现更佳,因为for循环内部较短且不需要在每次迭代中实例化StringBuilder

12
出于性能考虑,建议使用 StringBuilder 类来代替简单字符串拼接。 - Jeremy
6
编译器会优化字符串连接,但每次执行循环时您都会创建一个新的 StringBuilder 对象。 - Pedro Henriques
4
对于使用 +=,在这种情况下它会导致性能问题。在需要重复附加字符串时,应始终使用StringBuilder。 - starblue
11
这个解决方案在字符串末尾添加了一个额外的"\t",这通常是不希望的,特别是当您想创建逗号分隔列表或类似的内容时。我认为这里发布的其他解决方案使用Apache Commons或Guava更优秀。 - Peter Goetz
3
иҝҷдёҚжҳҜжңҖеҘҪзҡ„зӯ”жЎҲгҖӮиҜ·жҹҘзңӢVitaliiзҡ„JavaжҲ–Geewaxзҡ„Androidд»ҘиҺ·еҸ–жӣҙеҘҪзҡ„зӯ”жЎҲгҖӮ - Gibolt
显示剩余6条评论

256

下载 Apache Commons Lang 并使用该方法

 StringUtils.join(list)

 StringUtils.join(list, ", ") // 2nd param is the separator.

当然你可以自己实现它,但是他们的代码经过了充分测试,并且可能是最好的实现。

我是Apache Commons库的忠实粉丝,同时认为它是Java标准库的重要补充。


66
很不幸,SO的居民更喜欢重新发明轮子。 - skaffman
34
同样也可以在Apache Commons Lang中找到这个功能。使用方法如下:StringUtils.join(list.toArray(),"\t") - Muhd
37
这个特定的轮子不值得额外的依赖。 - Seva Alekseyev
25
我认为这是有争议的。如果你立即添加这个依赖项,你将经常使用它的类,而不是不使用。你将使用“if string!= null && string.trim()!= “”,而是使用StringUtils.isNotEmpty...您将使用ObjectUtils.equals(),以便您不必在任何地方检查null。但是,如果你等待只有一个依赖关系能够正当使用这些库,你可能永远不会用到它们。 - Ravi Wallau
7
太好了,它不会在结尾添加额外的标签页! - keuleJ
显示剩余6条评论

141

这是一个相当古老的问题,但我认为我可以提供一个更现代的答案 - 使用Joiner类来自Guava

String joined = Joiner.on("\t").join(list);

2
这是我最喜欢的解决方案。:-> +1 for guava. - csgeek
在程序中搜索Guava。谢谢! - Priidu Neemre
List<String> list = new ArrayList<String>(); list.add("Hello"); list.add("Hai"); list.add("Hi"); System.out.println(list.toString().substring(1, list.toString().length()-1).replaceAll(",", "\t")); - Lova Chittumuri
Guava Joiner 的美妙之处在于它还可以使用 Joiner.on(", ").withKeyValueSeparator(": ").join(myMap) 连接 Map。 - karmakaze

94

将可读的、有意义的字符串转换为列表是每个人都可能遇到的常见问题。

情况1。如果您的类路径中有apache的StringUtils(如来自rogerdpack和Ravi Wallau):

import org.apache.commons.lang3.StringUtils;
String str = StringUtils.join(myList);

案例2。如果你只想使用来自JDK(7)的方式:

import java.util.Arrays;
String str = Arrays.toString(myList.toArray()); 

永远不要自己造轮子,不要为这个一行代码的任务使用循环。


1
在发布我的解决方案之前,我没有注意到你的第二个情况的解决方案。你的实际上更好,并且节省了额外的步骤。我绝对会推荐它而不是一些需要额外工具的答案。 - Elliander
6
Arrays.toString(myList.toArray()) - 这是最简单和最直接的解决方案。将列表转化为数组并以字符串形式返回。 - Ondrej Bozek
我认为这应该被视为最佳和最有效的解决方案。 - Morey
"Arrays.toString(list.toArray())" 很容易编写,但效率非常低。 - Matthieu
虽然这看起来很简单,但用这种方式创建的字符串是"[a, b]",在转换回数组时可能会出现问题。我个人认为最好使用String.join(",", myList) - undefined

66

如果您需要一个简短的方法,在Java 5及以上版本中,您可以这样做:

myList.toString().replaceAll("\\[|\\]", "").replaceAll(", ","\t")

此外,如果你只是想打印出内容而不太关心“\t”,你可以简单地这样做:
myList.toString()

该函数返回一个字符串,如下所示:

[str1, str2, str3]

如果您有一个数组(不是ArrayList),则可以通过以下方式实现相同的效果:

 Arrays.toString(myList).replaceAll("\\[|\\]", "").replaceAll(", ","\t")

3
我认为这种方法比上面发布的方法更有效。太遗憾了,它显示在底部。它可能不够优雅,但我坚信你的解决方案的时间复杂度是O(n),而其他方法在理论上运行时间复杂度为O(n^2)(由于Java字符串是不可变的,在每次合并时基本上会创建一个新的字符串,随着结果字符串越来越大,情况变得更糟) - user3790827
如果文本很大,你最终会消耗计算机的资源。 - user3790827
这种方法比使用StringBuilder慢7-8倍。 - Zoka
@user3790827 你只是看到了表面。如果我告诉你在x个州的n个城市中的m个家庭中寻找一个人,你可能会说它是O(mnx)或者O(n^3),但是如果我改为“在这个国家中寻找这个人”,你会立刻告诉我是O(n)。此外,所有的算法都通常是O(n),没有循环内部循环。 - Jai

38

循环遍历并调用toString。没有什么神奇的方法,如果有的话,在底层它会做什么除了循环遍历?唯一的微小优化可能是使用StringBuilder而不是String,但即使如此也不会带来巨大的收益 - 字符串拼接在底层会转换为StringBuilder,但至少如果你以这种方式编写代码,你可以看到正在发生什么。

StringBuilder out = new StringBuilder();
for (Object o : list)
{
  out.append(o.toString());
  out.append("\t");
}
return out.toString();

谢谢!这就是我最终做的 :) - Juan Besa
4
对于大数组来说,这绝对不是微观优化。自动转换为使用StringBuilder是针对每个字符串连接单独完成的,对于循环的情况并没有帮助。如果您在一个表达式中连接了大量元素,则这是可以的。 - starblue
1
如果你要连接大约 50,000 个字符串并生成 ~1 MB 的结果字符串,显式地使用 StringBuilder 就非常重要了——这可能会导致执行时间从 1 秒变成 1 分钟的差异。 - Mr. Napik

36

大多数Java项目通常都有可用的apache-commons lang库。StringUtils.join()方法非常好用,而且有多种变体,可以满足几乎所有需求。

public static java.lang.String join(java.util.Collection collection,
                                    char separator)


public static String join(Iterator iterator, String separator) {
    // handle null, zero and one elements before building a buffer 
    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();
}

参数:

collection - 要连接在一起的值的集合,可以为null

separator - 要使用的分隔符字符

返回值: 连接后的字符串,如果输入为空迭代器则返回null

自从2.3版本以来


2
这非常好,因为join(...)方法对于数组和集合都有变体。 - vikingsteve
我需要创建包含很少填充元素和随机数字的字符串集合。最终我需要一个字符串,而不是数组或者数组转换成的字符串。 这是我的代码: Collections.shuffle(L); StringBuilder sb = new StringBuilder(); L.forEach(e -> sb.append(e)); return sb.toString(); - dobrivoje

19

对于这个简单的用例,您可以简单地使用逗号将字符串连接起来。如果您使用Java 8:

String csv = String.join("\t", yourArray);

否则,commons-lang库提供join()方法:
String csv = org.apache.commons.lang3.StringUtils.join(yourArray, "\t");

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