Raimund Krämer

Software Craftsman, Consultant, Coach

This post is based on an answer I posted on Stack Overflow. The code example was submitted as part of the question by Bruno Rozendo under CC BY-SA 3.0.
Note: The context is the StringBuilder class in Java. Code examples are in Java.


The accepted answer already explains how to do it. I would like to add some nuance about when and when not to do it. […]

I think that refactoring to a StringBuilder in cases like in the question is done for the wrong reasons. It’s a common case of cargo cult programming.

There is a common misconception that a string expression like this "It is " + LocalTime.now() + " o'clock"; causes a string concatenation for each “+” and thus O(n^2) time and space complexity.

However, Java uses a StringBuilder under the hood if all of the segments are known at compile time, even when some of them are calculated or formatted dynamically, like LocalTime.now() in this example. The fact that it implicitly uses a StringBuilder is actually an uninteresting implementation detail; the important thing is that it can determine the total length of the string as the sum of the individual string lengths, then allocate a single string and fill it once.

So when should a StringBuilder be used? If you pass a string builder to methods, which then append to it, or if you append conditionally (if (example) { stringBuilder.append("...") }), especially in a loop.

In your particular example, there is no performance penalty for using normal concatenation syntax vs. a StringBuilder, so there is no trade-off between readability and performance. I would argue that with some better formatting for the original version, your string will be much easier to read than when using a string builder. It is kind of subjective, but in my professional circles this generally tends to be considered to be true. I will add both examples next to each other and let you judge. The important point is to know why we make the decision.

Original code

this.path = DESTINY + deploy.name() + FILE_SEPARATOR + delivery.getSystem().getCode()
      + FILE_SEPARATOR + delivery.getName() + FILE_SEPARATOR + delivery.getEnviroment().getName();

With StringBuilder

StringBuilder tmpPath = new StringBuilder();
tmpPath.append(DESTINY);
tmpPath.append(deploy.name());
tmpPath.append(FILE_SEPARATOR);
tmpPath.append(delivery.getSystem().getCode());
tmpPath.append(FILE_SEPARATOR);
tmpPath.append(delivery.getName());
tmpPath.append(FILE_SEPARATOR);
tmpPath.append(delivery.getEnviroment().getName());
this.path = tmpPath.toString();

Original code with improved formatting (recommended)

this.path = DESTINY + deploy.name() + FILE_SEPARATOR
        + delivery.getSystem().getCode() + FILE_SEPARATOR
        + delivery.getName() + FILE_SEPARATOR
        + delivery.getEnviroment().getName();

Another option (recommended)

I recommend this option when joining string segments with a separator.

this.path = String.join(FILE_SEPARATOR,
        DESTINY + deploy.name(),
        delivery.getSystem().getCode(),
        delivery.getName(),
        delivery.getEnviroment().getName());