Core Java Best Practices for Beginners

In this article, we will discuss a few useful Java best practices that every Java beginner should know. It helps Java programmers write good code that conforms to the best practices.

1. Code for Interface, not for Implementation

1. Always use interface type as a reference type.

For example, use ListSetMap, etc interfaces as a reference type.
// Better
List<String> list = new ArrayList<>(); 

// Avoid
ArrayList<String> list = new ArrayList<>();

// Better
Set<String> set = new HashSet<>();

//Avoid
HashSet<String> employees = new HashSet<>();

// Better
Map<String,String> map = new HashMap<>();

//Avoid
HashMap<String,String> map = new HashMap<>();
By declaring a collection using an interface type, the code would be more flexible as you can change the concrete implementation easily when needed, for example:
List<String> list = new LinkedList<>(); 
When your code is designed to depend on the List interface, then you can swap among List’s implementations with ease, without modifying the code that uses it.

2. Always use interface type as a return type

For example,
public Collection listEmployees() {
    List<Employee> employees = new ArrayList<>();
    // add Employees to the list
    return employees;
}

3. Always use Interface Types as a method argument

public void foo(Set<Integer> numbers) {
}
The flexibility of using interface type for a collection is more visible in the case of the method’s parameters.

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

If you have more string concatenation operations, then prefer using the 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 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.

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!

3. 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 the 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
Check out Guide to String Best Practices in Java.

4. Java standard naming conventions

1. Packages naming conventions

A package should be named in lowercase characters. There should be only one English word after each dot.

2. Classes naming conventions

Class names should be nouns in UpperCamelCase (in mixed case with the first letter of each internal word capitalized). Try to keep your class names simple and descriptive.

3. Interfaces naming conventions

In Java, interfaces names, generally, should be adjectives. Interfaces should be in title case with the first letter of each separate word capitalized. 

4. Methods naming conventions

Methods always should be verbs. They represent action and the method name should clearly state the action they perform. The method name can be single or 2-3 words as needed to clearly represent the action. Words should be in camel case notation.

5. Variables naming conventions

The variable name should start with a lowercase letter. Parameter names, member variable names, and local variable names should be written in lowerCamelCase.

6. Constants naming conventions

Constant variable names should be written in upper characters separated by underscores. These names should be semantically complete and clear.
Read more about Java standard naming conventions with an example at Java Naming Conventions - Best Practices.

5. Return empty collections or arrays, not nulls

Some APIs intentionally return a null reference to indicate that instances are unavailable. This practice can lead to denial-of-service vulnerabilities when the client code fails to explicitly handle the null return value case.

If a method is designed to return a collection, it should not return null in case there’s no element in the collection. Consider the following method:
public List<Student> findStudents(String className) {
    List<Student> listStudents = null;
 
    if (//students are found//) {
        // add students to the lsit
    }
 
    return listStudents;
}
Here, the method returns null if no student is found. The key point here is, a null value should not be used to indicate no result. The best practice is, returning an empty collection to indicate no result. The above code can be easily corrected by initializing the collection:
List<Student> listStudents = new ArrayList<>;
Or
Collections.empty();
In summary, never return null in place of an empty array or collection. It makes your API more difficult to use and more prone to error, and it has no performance advantages.


6. Clean up resources in a finally block or use a try-with-resource statement

A common mistake occurs when closing the resources. The most common mistake is to close the resource at the end of the try block. 
For Example:
public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
     try {
          File file = new File("./tmp.txt");
          inputStream = new FileInputStream(file);

          // use the inputStream to read a file

          // do NOT do this
          inputStream.close();
        } catch (FileNotFoundException e) {
            LOGGER.error(e.getMessage());
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
        }
 }
The problem is that this approach seems to work perfectly fine as long as no exception gets thrown. All statements within the try block will get executed, and the resource gets closed. The problem is when an exception occurs within a try block and you might not reach the end of the try block. And as a result, you will not close the resources. You should, therefore, put all your clean up code into the finally block or use a try-with-resources statement.
You can use either finally block or Java 7’s Try-With-Resource Statement to close the resources. 
Let's write some simple examples to demonstrate this:
Use finally block
public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);

        // use the inputStream to read a file

       } catch (FileNotFoundException e) {
           LOGGER.error(e.getMessage());
       } finally {
            if (inputStream != null) {
                try {
                   inputStream.close();
                } catch (IOException e) {
                    LOGGER.error(e.getMessage());
                }
            }
       }
}
Java 7’s Try-With-Resource Statement
Syntax:
try(// open resources here){
    // use resources
} catch (FileNotFoundException e) {
 // exception handling
}
// resources are closed as soon as try-catch block is executed.
Example:
public void automaticallyCloseResource() {
 
     // Example 1
        File file = new File("./tmp.txt");
        try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
  
        } catch (FileNotFoundException e) {
             LOGGER.error(e.getMessage());
        } catch (IOException e) {
            LOGGER.error(e.getMessage());
        }
 
        // Example 2
       try (BufferedReader br = new BufferedReader(new FileReader(
                                                  "C:\\ramesh.txt"))) {
                System.out.println(br.readLine());
       } catch (IOException e) {
               e.printStackTrace();
       }
}
try with resources benefits:
  • More readable code and easy to write.
  • Automatic resource management.
  • The number of lines of code is reduced.
  • No need to finally block just to close the resources.
  • We can open multiple resources in a try-with-resources statement separated by a semicolon. For example, we can write the following code:
try (BufferedReader br = new BufferedReader(new FileReader(
  "C:\\ramesh.txt"));
        java.io.BufferedWriter writer = 
        java.nio.file.Files.newBufferedWriter(FileSystems.getDefault().
        getPath("C:\\journaldev.txt"), Charset.defaultCharset())) {
        System.out.println(br.readLine());
} catch (IOException e) {
       e.printStackTrace();
}
It's recommended to use try-with-resources statement close the resources over the try-finally block.


7. 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 a 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.


8. Use enums instead of int constants

An enum type is a special data type that enables a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST), the days of the week.

What is a problem with using int constants?

Before enum types were added to the language, a common pattern for representing enumerated types was to declare a group of named int constants, one for each member of the type:
    // int enum pattern - deficient!
    public static final int APPLE_FUJI          = 0;
    public static final int APPLE_PIPPIN        = 1;
    public static final int APPLE_GRANNY_SMITH  = 2;
    public static final int ORANGE_NAVEL        = 0;
    public static final int ORANGE_TEMPLE       = 1;
    public static final int ORANGE_BLOOD        = 2;
This technique, known as the int enum pattern, has many shortcomings.
From the above code, we can find int enum pattern is very deficient in following different way:
  • No way of type safety and no convenience.
  • The compiler won't complain if you pass an apple to a method that expects orange
  • int enums are compile-time constants. They are compiled into the clients that use them.
  • If the int associated with an enum constant is changed, its clients must be recompiled.
  • no easy to translate int enum constants into printable strings (all you see is a number)
You may encounter a variant of this pattern in which String constants are used in place of int constants. This variant, known as the String enum pattern, is even less desirable. While it does provide printable strings for its constants, it can lead naive users to hard-code string constants into client code instead of using field names.

What is the solution?

Java provides an alternative that avoids all the shortcomings of the int and string enum patterns and provides many added benefits. It is the enum type. Here’s how it looks in its simplest form:
    public enum Apple {
        FUJI, PIPPIN, GRANNY_SMITH
    }

    public enum Orange {
        NAVEL, TEMPLE, BLOOD
    }
Java's enum types are full-fledged classes, more powerful than in other languages. 
Advantages of using Enum Type:
  • They're classes that export one instance for each enum constant via a public static final field.
  • They are a generalization of singletons
  • Enums provide compile-time type safety
  • You can translate enums into printable strings by calling the toString() method
  • Provide implementations of Object methods
  • Implement Comparable, Serializable
Let's look at the rich enum type with Planet as an example. To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields. Enums are by their nature immutable, so all fields should be final. Fields can be public, but it is better to make them private and provide public accessors.
This program takes the earth weight of an object (in any unit) and prints a nice table of the object’s weight on all eight planets (in the same unit):
public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS(4.869e+24, 6.052e6),
    EARTH(5.975e+24, 6.378e6),
    MARS(6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN(5.685e+26, 6.027e7),
    URANUS(8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass;
    private final double radius;
    private final double surfaceGravity;

    // universal gravitational constant in m^3 / kg s^2
    private static final double G = 6.67300E-11;

    Planet(double mass, double radius) {
       this.mass = mass;
       this.radius = radius;
       surfaceGravity = G * mass / (radius * radius);
    }

    public double getMass() {
        return mass;
    }

    public double getRadius() {
        return radius;
    }

    public double getSurfaceGravity() {
        return surfaceGravity;
    }

    public double surfaceweight(double mass) {
        return mass * surfaceGravity;
    }
 
    private static void testWeight() {
        double earthWeight = 175.0;
        double mass = earthWeight / Planet.EARTH.getSurfaceGravity();

        System.out.println("Your earth weight = " + earthWeight);
        for(Planet p : Planet.values()) {
            System.out.println("Your Weight on " + p.toString() + " is " + p.surfaceweight(mass));
        }
    }

    public static void main(String[] args) {
        testWeight();
    }
}
Output:
Your earth weight = 175.0
Your Weight on MERCURY is 66.13367201195054
Your Weight on VENUS is 158.38392626475218
Your Weight on EARTH is 175.0
Your Weight on MARS is 66.43069946746834
Your Weight on JUPITER is 442.6939017602279
Your Weight on SATURN is 186.46496971259847
Your Weight on URANUS is 158.3497094835828
Your Weight on NEPTUNE is 198.84611594678634
Read more about enums on Java Enums Tutorial

9. Use PreparedStatement over Statement Interface

PreparedStatement gives more benefits over using Statement interface like
  • Prepared Statement is used for executing a precompiled SQL statement. java.sql.PreparedStatement is suitable for executing DML commands: SELECT, INSERT, UPDATE and DELETE
  • Prepared Statement is faster as compared to Statement because it is used for executing pre-compiled SQL statements. Hence, the same SQL query can be executed repeatedly in Prepared Statement which improves the performance.
  • PreparedStatement allows you to write a dynamic and parametric query.
  • PreparedStatement prevents SQL Injection attacks in Java.
Here is an example of how to use PreparedStatement in Java:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class PreparedStmtExample {

 // JDBC Driver Name & Database URL
 static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
 static final String JDBC_DB_URL = "jdbc:mysql://localhost:3306/tutorialDb";
 // JDBC Database Credentials
 static final String JDBC_USER = "root";
 static final String JDBC_PASS = "admin@123";
 public static void main(String[] args) {
     try {
          Class.forName(JDBC_DRIVER);  
          Connection connObj = DriverManager.getConnection(JDBC_DB_URL, JDBC_USER, JDBC_PASS);
          PreparedStatement prepStatement = connObj
         .prepareStatement("SELECT DISTINCT loan_type FROM bank_loans WHERE bank_name=?");
          prepStatement.setString(1, "Citibank");
          ResultSet resObj = prepStatement.executeQuery();
          while(resObj.next()) {
              System.out.println("Loan Type?= " + resObj.getString("loan_type"));
          }
      } catch (Exception sqlException) {
          sqlException.printStackTrace();
      }
  }
}

Check out 10 JDBC Best Practices

10. Favor using forEach() with Lambda expressions

Since Java 8, every collection now provides the forEach() method that encapsulates the iteration code inside the collection itself (internal iteration), and you just pass a Lambda expression to this method. This makes the iteration code even more compact, more flexible and more powerful. Here’s an example:
List<String> fruits = Arrays.asList("Banana", "Lemon", "Orange", "Apple");
 
fruits.forEach(fruit -> System.out.println(fruit));
This is equivalent to the following enhanced for loop:
for (String fruit : fruits) {
    System.out.println(fruit);
}
So I encourage you to use the forEach() method for iterating a collection in a way that helps you focus on your code, not on the iteration.


More Java/JavaEE best practices

Java Best Practices

JavaEE Best Practices

Comments