When I am working on legacy code and run across instances of StringBuffer, I typically replace them with instances of StringBuilder. Although a performance advantage can be gained from this change, I often change it in places I know will have little noticeable effect in terms of performance. I feel it's worth making the change for a variety of reasons in addition to the potential for performance benefit. There's rarely a reason to not choose StringBuilder
over StringBuffer
(API expectations are the most common exception) and the existence of StringBuffer
in code misleads and provides a bad example to those new to Java.
In the book The Pragmatic Programmer: From Journeyman to Master, Andy Hunt and David Thomas discuss "the importance of fixing the small problems in your code, the 'broken windows'." Jeff Atwood touched on this subject in the post The Broken Window Theory and it has been more recently addressed in the posts Software Rot, Entropy and the Broken Window Theory and Don't leave broken windows. The presence of StringBuffer
implies a staleness in the code. In effect, use of StringBuffer
may not be a "broken window," but it's a really old, leaky single-pane window that should be replaced with a modern, energy-efficient double-pane window.
I found Peter Lawrey's recent blog post StringBuffer, and how hard it is to get rid of legacy code to be an interesting take on other implications of StringBuffer
that still exist in code. Lawrey quotes the last paragraph of the StringBuffer class Javadoc documentation, "As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, StringBuilder. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization." Lawrey then uses simple Java methods and jmap to demonstrate that instances of StringBuffer
are still used in classes and libraries delivered with the JDK even as late as Java 8.
Lawrey points out that the presence of StringBuffer
in frequently used Java code more than a decade after the introduction of "drop-in replacement" StringBuilder
is evidence of how difficult it is to "clean up legacy code." Lawrey's full conclusion states, "Using StringBuffer
on startup doesn’t make much difference, but given it has a well known, drop in replacement, and it is still used, even in new functionality more than ten years later shows how hard it can be to clean up legacy code or to change thinking to get people to use best practice libraries."
I decided to try out one of Lawrey's simplest examples when compiled with Java 8 Update 121 and when compiled with a recent release of OpenJDK 9. I (slightly) adapted Lawrey's example to the simple "Main" class listing shown next.
Main.java
import java.io.IOException; /** * (Slightly) adapted class from blog post * "StringBuffer, and how hard it is to get rid of legacy code" at * https://vanilla-java.github.io/2017/04/13/String-Buffer-and-how-hard-it-is-to-get-rid-of-legacy-code.html */ public class Main { /** * Main function that instantiates this Java "application" and does nothing * else until "ENTER" is pressed. */ public static void main(final String[] args) throws IOException { System.out.println("Waiting [press ENTER to exit] .."); System.in.read(); } }
The following screen snapshot shows the output of using jcmd with its -all
option (includes unreachable objects in the inspection) to show the number of instances of StringBuffer
and StringBuilder
in the simple Java application when compiled and run against three different versions of Java (Java 8 Update 102, Java 8 Update 121, and OpenJDK 9.0 ea+164). The execution of jcmd is performed in PowerShell and so Select-String is used similarly to how grep is used in Linux.
Although the versions of the class compiled and executed with versions of Java 8 had instances of StringBuffer
, the version compiled with and executed against Java 9 only had instances of StringBuilder
. It looks like the resolution of JDK-8041679 ("Replace uses of StringBuffer with StringBuilder within core library classes") and JDK-8043342 ("Replace uses of StringBuffer with StringBuilder within crypto code") have had their intended effect.
2 comments:
Error Prone's Bug Pattern called "JdkObsolete" is used to "suggest alternatives to obsolete JDK classes" and includes suggesting alternatives for use of StringBuffer.
JDK-8229022 ("BufferedReader performance can be improved by using StringBuilder") has been resolved in JDK 14 Early Access Build 17. The bug description states, "BufferedReader uses StringBuffer. Swapping that to use StringBuilder seems to give a ~13% performance boost." The bug report also references How fast can a BufferedReader read lines in Java? for additional background details.
Post a Comment