7

In JDK 8, StringBuffer class has a toStringCache, while StringBuilder doesn't.

/**
 * A cache of the last value returned by toString. Cleared
 * whenever the StringBuffer is modified.
 */
private transient char[] toStringCache;

But why?

  • One possible reason I can think of is that StringBuffer is already synchronized so a cache can be implemented easier.

  • Or maybe historically StringBuffer was implemented this way so old code depends heavily on this feature?

Given modern JVM with escape analysis and biased locking, is the difference relevant anymore?

Naman
  • 27,789
  • 26
  • 218
  • 353
ntysdd
  • 1,206
  • 2
  • 9
  • 19

2 Answers2

7

It might help to consider the historical context. StringBuilder was introduced with Java 5, since it has been recognized, that StringBuffer isn’t well suited for its actual use cases.

The newly introduced StringBuilder has been designed for the major use case of being constructed, used and immediately dropped afterwards, in a purely local context. Therefore, it doesn’t provide any synchronization and it doesn’t bother optimizing the rare case of its toString() method being called multiple times without an in-between change (when does this happen in real life at all?), especially as, indeed, providing the caching feature without sacrificing the performance advantage of no thread synchronization, is somewhere between “hard” to “impossible”.

While StringBuilder is documented to be not thread safe, so you know inconsistent things could happen when calling methods on it concurrently, the class String is guaranteed to be thread safe through immutability, hence, it must not be allowed that StringBuilder’s absence of synchronization can causes inconsistencies in already constructed strings and, not sharing the array between String and StringBuilder at all, is the safest solution.

So why is this optimization there, if it hardly ever has a benefit in real life? Well, because it’s there since a very long time, most likely even since Java 1.0 and it’s not worth changing anything in the class StringBuffer. Its presence may not have any real advantage, but neither has removing it, which would require new testing and so on and could turn out to be the space bar overheating feature for some application…

You may notice that back-then, in Java 1.x, a lot of design decision were made that may look strange today. Overusing synchronized in fundamental classes is one of them, this hardly ever helping optimization another one. At that time, even the implications of immutability were not well understood, which is why we have redundant methods like String.valueOf(char[]) and String.copyValueOf(char[]), plus the opportunity to use new String(char[])

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I didn't know String.copyValueOf(char[]) – ntysdd Sep 19 '17 at 12:36
  • 1
    @ntysdd: …and the existence of these redundant `copyValueOf` methods is something we don’t need to remember. At least for our daily programmer’s work. – Holger Sep 19 '17 at 13:09
  • 2
    @ntysdd (and @Holger) Certainly `copyValueOf` methods are irrelevant today. Of historical note is that pre JDK 1.0, the `String(char[])` constructor and `valueOf(char[])` methods (both public) simply stored the reference to the argument in a newly constructed string. Naturally this led to mutable strings! The `copyValueOf` methods were added to copy the arrays, then the constructors and `valueOf` were modified to make copies too, leaving the `copyValueOf` methods redundant. However, they were never cleaned up. – Stuart Marks Sep 19 '17 at 18:55
  • @StuartMarks Horrible. – ntysdd Sep 20 '17 at 10:36
4

I think that your first guess is highly accurate, since StringBuilder is not thread-safe and an instance can be shared across multiple threads, implementing such a cache would require additional synchronization, this would defeat the purpose of StringBuilder in the first place.

As to why this would be needed, it boils down to the new String(...) constructor that is used; in case of StringBuffer that used the constructor String(array, boolean) the comment says:

Package private constructor which shares value array for speed.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    Might be worth noting that the speed advantage does not materialize if you call `StringBuffer.toString` a single time, as [the caller](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/StringBuffer.java#669) did a copy then. There could be a small benefit, only in the rare case of multiple invocations without changes. This `String` constructor provides more advantages for factories directly working on a correctly sized `char[]` buffer without reusing it afterwards, say `String.concat(String)`, `String.replace(char,char)`, `Integer.toString(int)`, etc. – Holger Sep 19 '17 at 09:41