Guide to String Best Practices in Java

1. Overview

In this guide, I would like to explain the best practices for String Handling. This post belongs to Java Best Practices Series category. Basically, this guide explains how effectively we can use String in the day to day project work.
Check out beginners to expert String Tutorial/Guide at Java String API Guide/Tutorial

2. String Best Practices in Java

Let's discuss each String best practices with an example.

2.1. Use StringBuilder or StringBuffer for string concatenations instead of the + operator

If you have more string concatenation operations, then prefer using StringBuilder object over the + operator.

StringBuffer acts like StringBuilder, as it has the same methods except they are synchronized. That means StringBuffer should be used in multi-threading context.

Let's write a simple program to demonstrate the performance of String, StringBuilder, and StringBuffer.
public class PerformanceTest{

     public static void main(String []args){
        String str = "";
        long startTime = System.nanoTime();
        for(int i=0 ; i < 10 ; i++) {
           str = str + i;
        }
       long endTime = System.nanoTime();
       System.out.println(String.format("String opetation with 
                + operator took [%d] nano seconds",(endTime-startTime)));
  
       StringBuilder builder = new StringBuilder();
       startTime = System.nanoTime();
       for(int i=0;i<10;i++) {
           builder.append(i);
       }
       endTime = System.nanoTime();
       System.out.println(String.format("String opetation with 
                StringBuilder took [%d] nano seconds",(endTime-startTime)));
  
       StringBuffer strBuffer = new StringBuffer();
       startTime = System.nanoTime();
       for(int i=0;i<10;i++) {
           strBuffer.append(i);
       }
       endTime = System.nanoTime();
       System.out.println(String.format("String opetation with 
            StringBuffer took [%d] nano seconds",(endTime-startTime)));
  
   }
}
Output:
String opetation with + operator took [86037] nano seconds
String opetation with StringBuilder took [7928] nano seconds
String opetation with StringBuffer took [14165] nano seconds
From above result note that StringBuilder and StringBuffer for String concatenation are more faster than String concatenation using + operator.

Eclipse Tip

Did you know, in Eclipse you can automatically convert '+' String concatenations to StringBuilder append()s? It also picks the right append() depending on the type. Ctrl+1 is very handy!

2.2. Compare Two String by equals() Method Instead == Operator

Refer below points while comparing string contents and string references.
  1. use == to compare primitive e.g. boolean, int, char etc, while use equals() to compare objects in Java. 
  2. == return true if two references are of the same object. The result of equals() method depends on overridden implementation. 
  3. For comparing the content of two Strings use equals() instead of == equality operator. 
Simple program to demonstrate compare two string by equals() method instead == operator.

public class StringEqualsTest{

     public static void main(String []args){
        String s1 = "string"; 
        String s2 = "string"; 
        String s3 = new String("string");
  
        System.out.println(" == opearator result for s1 and s2 : " + (s1 == s2));
        System.out.println(" == opearator result for s1 and s3 : " + (s1 == s3));
  
        System.out.println(" equals() method result for s1 and s2 : " + s1.equals(s2));
   
        System.out.println(" equals() method result for s1 and s3 : " + s1.equals(s3));  
     }
}
Output:
 == opearator result for s1 and s2 : true
 == opearator result for s1 and s3 : false
 equals() method result for s1 and s2 : true
 equals() method result for s1 and s3 : true

2.3. Call .equals() Method on Known String Constants Rather Than Unknown Variable

If you know some constants are fixed then use equals method on know constants rather than unknown variable. Some time variable may contain null and if you are calling equals method on null contains variable leads to null pointer exception.
public class ConstantEqualsTest{
    private static final String CONSTANT = "constant value";

     public static void main(String []args){
           processString("constant value");
 
     }
     private static void processString(String str){
         if(CONSTANT.equals(str)){
            System.out.println("CONSTANT.equals(string): " 
            + CONSTANT.equals(str));
         }
     }
}
Output:
CONSTANT.equals(string): true

2.4. Prefer switch( ) Statement Instead of Multiple if-else-if

Java 1.7 introduces the switch statement for Strings. If there is the scenario to compare multiple strings then use switch over multiple if-else-if statements. Here is a comparison of the performance of these two approaches.
public class Test {
 public static final String CONSTANT = "someConstant";
 
 public static void main(String[] args) {
     Test test = new Test();
     long startTime = System.nanoTime();
     test.convertStringToIntegerWithSwitch("FOUR");
     long endTime = System.nanoTime();
     System.out.println(String.format("String comparison with Switch took [%d] nano seconds.",(endTime-startTime)));
  
     startTime = System.nanoTime();
     test.convertStringToIntegerWithIf("FOUR");
     endTime = System.nanoTime();
     System.out.println(String.format("String comparison with If took [%d] nano seconds.",(endTime-startTime)));
 }
 
 private int convertStringToIntegerWithSwitch(String stringNumber) {
     switch(stringNumber) {
     case "ZERO" :
     return 0;
     case "ONE":
     return 1;
     case "TWO":
     return 2;
     case "THREE":
     return 3;
     default :
     return -1;
   }
 }
 
 private int convertStringToIntegerWithIf(String stringNumber) {
     if("ZERO".equals(stringNumber)) {
        return 0;
     } else if("ONE".equals(stringNumber)) {
        return 1;
     } else if("TWO".equals(stringNumber)) {
       return 2;
     } else if("THREE".equals(stringNumber)) {
       return 3;
     } else {
       return -1;
     } 
  }
}
That means using switch we can improve the performance, from the above result it is clear that comparison with if-else takes double time than the time taken by the switch. This example has only 4 comparisons, if there are more than 4 if-else then switch will give a better result than if-else.

2.5. Use String.valueOf() Instead of toString()

If object needs to be converted to string then the result of obj.toString() and String.valueOf(obj) will be same but String.valueOf() is null safe, means it will never throw NullPointerException.
Test test = null;
// Below statement will not throw NPE
System.out.println(String.valueOf(test));
// Next statement will throw NPE
System.out.println(test.toString())
means if we are sure the object will never be null then we should always use toString() otherwise, String.valueOf() is preferable.

2.6. Use String Utility Classes

Prefer StringUtility classes from different popular libraries because these Utility classes are tested and well-known libraries.
I have created my own String Utility classes and have used in tons of projects.
You should refer these utility classes or methods:
  1. Java String Utility Class
  1. 27 Useful String Utility Methods

2.7 Avoid Duplicate Literals

Code containing duplicate String literals can usually be improved by declaring the String as a constant field.
Example:
private void bar() {
     String howdy= "Howdy"
     buz(howdy);
     buz(howdy);
 }

 private void buz(String x) {}

// Better
private static final String HOWDY = "Howdy";
private void bar() {
     buz(HOWDY);
     buz(HOWDY);
 }
 private void buz(String x) {}

Other Basic String Best Practices

Unnecessary Case Change

Using equalsIgnoreCase() is faster than using toUpperCase() or toLowerCase().equals()

Append Character With Char

Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods.
Example:
StringBuffer sb = new StringBuffer(); 
sb.append("a");                        // avoid this
StringBuffer sb = new StringBuffer();
sb.append('a');             // use this instead

Consecutive Appends Should Reuse

Consecutive calls to StringBuffer/StringBuilder.append should be chained, reusing the target object. This can improve the performance by producing a smaller bytecode, reducing overhead and improving inlining. A complete analysis can be found here
Example:
String foo = " "; 
StringBuffer buf = new StringBuffer();
buf.append("Hello"); // poor
buf.append(foo);
buf.append("World");

StringBuffer buf = new StringBuffer();
buf.append("Hello").append(foo).append("World"); // good

Avoid StringBuffer Field

StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaks if held within objects with long lifetimes.
Example:
public class Foo {
 private StringBuffer buffer;    
 // potential memory leak as an instance variable;
}

Unnecessary Conversion Temporary

Avoid unnecessary temporaries when converting primitives to Strings.
Example:
public String convert(int x) { 
  // this wastes an object
  String foo = new Integer(x).toString();
  // this is better
  return Integer.toString(x);
}
Let me know if you know any other best practices for Strings in Java.

Eclipse Tip - Use MessageFormatter

Do you prefer your strings concatenations 'printf' style? Eclipse can do it for you automatically!.

3. Conclusion

This post explained the best practices for String API. Learn more Java best practices on Java Best Practices Series. Let us know if you know any other best practices for String Handling in Java.

Feel free to comment on this post and give us a suggestion or feedback.

Comments

  1. Very helpful, productive and informative article. I highly recommend this article to everyone.

    ReplyDelete

Post a Comment