String Best Practices in Java

In this blog post, I would like to explain the best practices for String Handling. This post belongs to the Java Best Practices Series category. Basically, this guide explains how effectively we can use String in day-to-day Java projects.

Strings are one of the most used data types in Java, but if not handled correctly, they can lead to performance issues and cumbersome code. This blog post outlines the best practices for working with strings in Java, ensuring efficient and maintainable code.
Check out beginners to expert String Tutorial/Guide at Java String API Guide/Tutorial

1. Use String Literals Where Possible

Using string literals allows Java to make use of the String Pool, leading to better performance.
String str = "JavaGuides"; // Uses String Pool 

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

Concatenating strings using the + operator in a loop can be highly inefficient. Use StringBuilder, or StringBuffer instead.

StringBuffer acts like StringBuilder, as it has the same methods except they are synchronized. That means StringBuffer should be used in a 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 the above result note that StringBuilder and StringBuffer for String concatenation are faster than String concatenation using + operator.

3. Use equals() Method for String Comparison Instead == Operator

To compare the content of strings, always use the equals() method instead of the == operator.

Refer below points while comparing string contents and string references.
  • use == to compare primitives e.g. boolean, int, char, etc, while use equals() to compare objects in Java.
  • == return true if two references are pointing to the same object. The result of the equals() method depends on overridden implementation.
  • For comparing the content of two Strings use equals() instead of == equality operator. 
Here is a simple program to demonstrate comparing two strings by the 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
In summary, to compare the content of strings, always use the equals() method, not ==.2.3. 

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

If you know some constants are fixed then use the equals method on known constants rather than unknown variables. Sometimes variable may contain null and if you are calling the equals method on null contains variable leads to a 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

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

Java 1.7 introduces the switch statement for Strings. If there is a 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 the 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 the switch will give a better result than if-else.

6. Be Careful with Case Sensitivity 

Use equalsIgnoreCase() if you want to compare strings without considering their case.
if(str1.equalsIgnoreCase(str2)) {
    // Strings are equal, ignoring case
}

7. Prefer isEmpty to Check for Empty Strings

Instead of comparing the length of a string to zero, use the isEmpty() method
if(str.isEmpty()) {
    // String is empty
}

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

If an object needs to be converted to a string then the result of obj.toString() and String.valueOf(obj) will be the same but String.valueOf() is null safe, which 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())
It means if we are sure the object will never be null then we should always use toString() otherwise, String.valueOf() is preferable.

9. 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 them in tons of projects.
You should refer to these utility classes or methods:
  1. Java String Utility Class
  1. 27 Useful String Utility Methods

10. 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) {}

11. Other Basic String Best Practices

Unnecessary Case Change

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

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.

Conclusion 

String handling in Java is powerful but requires attention to best practices to avoid common pitfalls and inefficiencies. By following the above guidelines, developers can write code that's more efficient, readable, and maintainable. Whether working on large-scale applications or small projects, these best practices are essential for anyone dealing with strings in Java.

Learn more about Java best practices in Java Best Practices Series. Let me know if you know any other best practices for String Handling in Java.

Comments

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

    ReplyDelete
  2. Great and productive article for Java beginners to know String best practices.

    ReplyDelete

Post a Comment

Leave Comment