StringBuilder vs String concatenation in toString() in Java

后端 未结 18 2184
北恋
北恋 2020-11-21 04:18

Given the 2 toString() implementations below, which one is preferred:

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


        
相关标签:
18条回答
  • 2020-11-21 05:01

    Version 1 is preferable because it is shorter and the compiler will in fact turn it into version 2 - no performance difference whatsoever.

    More importantly given we have only 3 properties it might not make a difference, but at what point do you switch from concat to builder?

    At the point where you're concatenating in a loop - that's usually when the compiler can't substitute StringBuilder by itself.

    0 讨论(0)
  • 2020-11-21 05:02

    Apache Commons-Lang has a ToStringBuilder class which is super easy to use. It does a nice job of both handling the append-logic as well as formatting of how you want your toString to look.

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

    Will return output that looks like com.blah.YourClass@abc1321f[a=whatever, b=foo].

    Or in a more condensed form using chaining:

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

    Or if you want to use reflection to include every field of the class:

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

    You can also customize the style of the ToString if you want.

    0 讨论(0)
  • 2020-11-21 05:05

    I also had clash with my boss on the fact whether to use append or +.As they are using Append(I still cant figure out as they say every time a new object is created). So I thought to do some R&D.Although I love Michael Borgwardt explaination but just wanted to show an explanation if somebody will really need to know in future.

    /**
     *
     * @author Perilbrain
     */
    public class Appc {
        public Appc() {
            String x = "no name";
            x += "I have Added a name" + "We May need few more names" + Appc.this;
            x.concat(x);
            // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
            //System.out.println(x);
        }
    
        public void Sb() {
            StringBuilder sbb = new StringBuilder("no name");
            sbb.append("I have Added a name");
            sbb.append("We May need few more names");
            sbb.append(Appc.this);
            sbb.append(sbb.toString());
            // System.out.println(sbb.toString());
        }
    }
    

    and disassembly of above class comes out as

     .method public <init>()V //public Appc()
      .limit stack 2
      .limit locals 2
    met001_begin:                                  ; DATA XREF: met001_slot000i
      .line 12
        aload_0 ; met001_slot000
        invokespecial java/lang/Object.<init>()V
      .line 13
        ldc "no name"
        astore_1 ; met001_slot001
      .line 14
    
    met001_7:                                      ; DATA XREF: met001_slot001i
        new java/lang/StringBuilder //1st object of SB
        dup
        invokespecial java/lang/StringBuilder.<init>()V
        aload_1 ; met001_slot001
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
    g/StringBuilder;
        ldc "I have Added a nameWe May need few more names"
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
    g/StringBuilder;
        aload_0 ; met001_slot000
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
    g/StringBuilder;
        invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
        astore_1 ; met001_slot001
      .line 15
        aload_1 ; met001_slot001
        aload_1 ; met001_slot001
        invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
    g;
        pop
      .line 18
        return //no more SB created
    met001_end:                                    ; DATA XREF: met001_slot000i ...
    
    ; ===========================================================================
    
    ;met001_slot000                                ; DATA XREF: <init>r ...
        .var 0 is this LAppc; from met001_begin to met001_end
    ;met001_slot001                                ; DATA XREF: <init>+6w ...
        .var 1 is x Ljava/lang/String; from met001_7 to met001_end
      .end method
    ;44-1=44
    ; ---------------------------------------------------------------------------
    
    
    ; Segment type: Pure code
      .method public Sb()V //public void Sb
      .limit stack 3
      .limit locals 2
    met002_begin:                                  ; DATA XREF: met002_slot000i
      .line 21
        new java/lang/StringBuilder
        dup
        ldc "no name"
        invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
        astore_1 ; met002_slot001
      .line 22
    
    met002_10:                                     ; DATA XREF: met002_slot001i
        aload_1 ; met002_slot001
        ldc "I have Added a name"
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
    g/StringBuilder;
        pop
      .line 23
        aload_1 ; met002_slot001
        ldc "We May need few more names"
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
    g/StringBuilder;
        pop
      .line 24
        aload_1 ; met002_slot001
        aload_0 ; met002_slot000
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
    g/StringBuilder;
        pop
      .line 25
        aload_1 ; met002_slot001
        aload_1 ; met002_slot001
        invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
        invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
    g/StringBuilder;
        pop
      .line 28
        return
    met002_end:                                    ; DATA XREF: met002_slot000i ...
    
    
    ;met002_slot000                                ; DATA XREF: Sb+25r
        .var 0 is this LAppc; from met002_begin to met002_end
    ;met002_slot001                                ; DATA XREF: Sb+9w ...
        .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
      .end method
    ;96-49=48
    ; ---------------------------------------------------------------------------
    

    From the above two codes you can see Michael is right.In each case only one SB object is created.

    0 讨论(0)
  • 2020-11-21 05:06

    See the example below:

    static final int MAX_ITERATIONS = 50000;
    static final int CALC_AVG_EVERY = 10000;
    
    public static void main(String[] args) {
        printBytecodeVersion();
        printJavaVersion();
        case1();//str.concat
        case2();//+=
        case3();//StringBuilder
    }
    
    static void case1() {
        System.out.println("[str1.concat(str2)]");
        List<Long> savedTimes = new ArrayList();
        long startTimeAll = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < MAX_ITERATIONS; i++) {
            long startTime = System.currentTimeMillis();
            str = str.concat(UUID.randomUUID() + "---");
            saveTime(savedTimes, startTime);
        }
        System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
    }
    
    static void case2() {
        System.out.println("[str1+=str2]");
        List<Long> savedTimes = new ArrayList();
        long startTimeAll = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < MAX_ITERATIONS; i++) {
            long startTime = System.currentTimeMillis();
            str += UUID.randomUUID() + "---";
            saveTime(savedTimes, startTime);
        }
        System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
    }
    
    static void case3() {
        System.out.println("[str1.append(str2)]");
        List<Long> savedTimes = new ArrayList();
        long startTimeAll = System.currentTimeMillis();
        StringBuilder str = new StringBuilder("");
        for (int i = 0; i < MAX_ITERATIONS; i++) {
            long startTime = System.currentTimeMillis();
            str.append(UUID.randomUUID() + "---");
            saveTime(savedTimes, startTime);
        }
        System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
    
    }
    
    static void saveTime(List<Long> executionTimes, long startTime) {
        executionTimes.add(System.currentTimeMillis() - startTime);
        if (executionTimes.size() % CALC_AVG_EVERY == 0) {
            out.println("average time for " + executionTimes.size() + " concatenations: "
                    + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                    + " ms avg");
            executionTimes.clear();
        }
    }
    

    Output:

    java bytecode version:8
    java.version: 1.8.0_144
    [str1.concat(str2)]
    average time for 10000 concatenations: 0.096 ms avg
    average time for 10000 concatenations: 0.185 ms avg
    average time for 10000 concatenations: 0.327 ms avg
    average time for 10000 concatenations: 0.501 ms avg
    average time for 10000 concatenations: 0.656 ms avg
    Created string of length:1950000 in 17745 ms
    [str1+=str2]
    average time for 10000 concatenations: 0.21 ms avg
    average time for 10000 concatenations: 0.652 ms avg
    average time for 10000 concatenations: 1.129 ms avg
    average time for 10000 concatenations: 1.727 ms avg
    average time for 10000 concatenations: 2.302 ms avg
    Created string of length:1950000 in 60279 ms
    [str1.append(str2)]
    average time for 10000 concatenations: 0.002 ms avg
    average time for 10000 concatenations: 0.002 ms avg
    average time for 10000 concatenations: 0.002 ms avg
    average time for 10000 concatenations: 0.002 ms avg
    average time for 10000 concatenations: 0.002 ms avg
    Created string of length:1950000 in 100 ms

    As the string length increases, so does the concatenation time.
    That is where the StringBuilder is definitely needed.
    As you see, the concatenation: UUID.randomUUID()+"---", does not really affect the time.

    P.S.: I don't think When to use StringBuilder in Java is really a duplicate of this.
    This question talks about toString() which most of the times does not perform concatenations of huge strings.


    2019 Update

    Since java8 times, things have changed a bit. It seems that now(java13), the concatenation time of += is practically the same as str.concat(). However StringBuilder concatenation time is still constant. (Original post above was slightly edited to add more verbose output)

    java bytecode version:13
    java.version: 13.0.1
    [str1.concat(str2)]
    average time for 10000 concatenations: 0.047 ms avg
    average time for 10000 concatenations: 0.1 ms avg
    average time for 10000 concatenations: 0.17 ms avg
    average time for 10000 concatenations: 0.255 ms avg
    average time for 10000 concatenations: 0.336 ms avg
    Created string of length:1950000 in 9147 ms
    [str1+=str2]
    average time for 10000 concatenations: 0.037 ms avg
    average time for 10000 concatenations: 0.097 ms avg
    average time for 10000 concatenations: 0.249 ms avg
    average time for 10000 concatenations: 0.298 ms avg
    average time for 10000 concatenations: 0.326 ms avg
    Created string of length:1950000 in 10191 ms
    [str1.append(str2)]
    average time for 10000 concatenations: 0.001 ms avg
    average time for 10000 concatenations: 0.001 ms avg
    average time for 10000 concatenations: 0.001 ms avg
    average time for 10000 concatenations: 0.001 ms avg
    average time for 10000 concatenations: 0.001 ms avg
    Created string of length:1950000 in 43 ms

    Worth noting also bytecode:8/java.version:13 combination has a good performance benefit compared to bytecode:8/java.version:8

    0 讨论(0)
  • 2020-11-21 05:07

    Using latest version of Java(1.8) the disassembly(javap -c) shows the optimization introduced by compiler. + as well sb.append() will generate very similar code. However, it will be worthwhile inspecting the behaviour if we are using + in a for loop.

    Adding strings using + in a for loop

    Java:

    public String myCatPlus(String[] vals) {
        String result = "";
        for (String val : vals) {
            result = result + val;
        }
        return result;
    }
    

    ByteCode:(for loop excerpt)

    12: iload         5
    14: iload         4
    16: if_icmpge     51
    19: aload_3
    20: iload         5
    22: aaload
    23: astore        6
    25: new           #3                  // class java/lang/StringBuilder
    28: dup
    29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
    32: aload_2
    33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    36: aload         6
    38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    44: astore_2
    45: iinc          5, 1
    48: goto          12
    

    Adding strings using stringbuilder.append

    Java:

    public String myCatSb(String[] vals) {
        StringBuilder sb = new StringBuilder();
        for(String val : vals) {
            sb.append(val);
        }
        return sb.toString();
    }
    

    ByteCdoe:(for loop excerpt)

    17: iload         5
    19: iload         4
    21: if_icmpge     43
    24: aload_3
    25: iload         5
    27: aaload
    28: astore        6
    30: aload_2
    31: aload         6
    33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    36: pop
    37: iinc          5, 1
    40: goto          17
    43: aload_2
    

    There is a bit of glaring difference though. In first case, where + was used, new StringBuilder is created for each for loop iteration and generated result is stored by doing a toString() call(29 through 41). So you are generating intermediate Strings that your really do not need while using + operator in for loop.

    0 讨论(0)
  • 2020-11-21 05:08

    I prefer:

    String.format( "{a: %s, b: %s, c: %s}", a, b, c );
    

    ...because it's short and readable.

    I would not optimize this for speed unless you use it inside a loop with a very high repeat count and have measured the performance difference.

    I agree, that if you have to output a lot of parameters, this form can get confusing (like one of the comments say). In this case I'd switch to a more readable form (perhaps using ToStringBuilder of apache-commons - taken from the answer of matt b) and ignore performance again.

    0 讨论(0)
提交回复
热议问题