给定下面的2个toString()实现,哪个是首选的:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

or

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

更重要的是,鉴于我们只有3个属性,它可能不会有什么不同,但在什么时候你会从+ concat切换到StringBuilder?


当前回答

从Java 1.5开始,使用"+"和StringBuilder.append()的简单一行连接生成完全相同的字节码。

因此,为了代码的可读性,请使用“+”。

2个例外:

多线程环境:StringBuffer 循环中的连接:StringBuilder/StringBuffer

其他回答

出于性能考虑,不鼓励使用+=(字符串连接)。原因是:Java String是一个不可变的,每当一个新的连接完成时,一个新的String就会被创建(新的String与已经在String池中的旧String具有不同的指纹)。创建新的字符串会给GC带来压力,并降低程序的运行速度:创建对象的开销很大。

下面的代码将使其更加实用和清晰。

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

下面报告了运行的结果。

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

不考虑1个连接的结果(JIT还没有完成它的工作),即使对于10个连接,性能惩罚也是相关的;对于成千上万的连接,差异是巨大的。

从这个非常快速的实验中得到的教训(很容易用上面的代码重现):永远不要使用+=将字符串连接在一起,即使是在需要一些连接的非常基本的情况下(正如前面所说,创建新字符串无论如何都是昂贵的,并且会给GC带来压力)。

Apache Commons-Lang有一个超级容易使用的ToStringBuilder类。它在处理附加逻辑以及格式化你想要的toString外观方面做得很好。

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

将返回类似com.blah的输出。YourClass@abc1321f (a =, = foo)。

或者使用链接的更浓缩的形式:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

或者如果你想使用反射来包含类的每个字段:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

如果需要,还可以自定义ToString的样式。

使toString方法尽可能具有可读性!

在我的书中,唯一的例外是,如果你能向我证明它消耗了大量的资源:)(是的,这意味着分析)

还要注意,Java 5编译器生成的代码比早期版本的Java中使用的手写“StringBuffer”方法更快。如果你使用“+”,这和未来的增强是免费的。

这是我在Java8中检查的

Using String concatenation Using StringBuilder long time1 = System.currentTimeMillis(); usingStringConcatenation(100000); System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms"); time1 = System.currentTimeMillis(); usingStringBuilder(100000); System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms"); private static void usingStringBuilder(int n) { StringBuilder str = new StringBuilder(); for(int i=0;i<n;i++) str.append("myBigString"); } private static void usingStringConcatenation(int n) { String str = ""; for(int i=0;i<n;i++) str+="myBigString"; }

如果对大量的字符串使用字符串连接,这真的是一场噩梦。

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

关键在于你是在一个地方写一个连接,还是在一段时间内把它积累起来。

对于您给出的示例,显式使用StringBuilder是没有意义的。(请查看第一个案例的编译代码。)

但是如果你正在构建一个字符串,例如在一个循环中,使用StringBuilder。

为了澄清,假设hugeArray包含数千个字符串,代码如下:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

与以下相比非常浪费时间和内存:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();