1. Handle Null Pointer Exceptions
Null pointer exceptions are quite common in Java and we need to handle it always. For example, if we are reading something from file and storing in a string variable. Then before performing any further action. Always check whether the value is null or not. Similarly check for Objects also. If the object is null then throw null pointer exception if its left unhandled then the code might behave weird.
//Let's say we are reading something from file
String str=null;
if(str==null)
{
throw new NullPointerException("value can't be null");
}
else
{
int len= str.length();
System.out.println(len);
}
2. Use equals() instead of ==
- == is a reference comparison, i.e. both objects point to the same memory location or not.
- .equals() evaluates to the comparison of values in the objects.
String city1 = "NewYork";
String city2 = "NewYork";
String city3 = new String("NewYork");
//do we have any difference between city1 & city2
System.out.println(city1 == city2);
System.out.println(city1.equals(city2));
//Now check do we have any difference between city2 & city3
System.out.println(city2 == city3);
System.out.println(city3.equals(city3));
//We will notice, city2 & city3 is failing when we use == operator That's because the address is different however when we compare the content it matches.
System.out.println(System.identityHashCode(city1));
System.out.println(System.identityHashCode(city2));
System.out.println(System.identityHashCode(city3));
I have measured the performance. The == operator takes less time than equals(). That should be obvious right, when we compare memory reference it should take less time however our purpose is never to compare the references, so always use equals method over ==.
3. Simplify the string search
String search can be simplified. We don’t have to iterate over characters or words to check whether substring exists in the string. indexOf() or contains() can be used. Performance wise indexOf gives slightly better performance however using contains method increases readability of the code.
String str="You are a good boy";
if(str.indexOf("good")==-1)
System.out.println("String not found");
else
System.out.println("String found at= "+str.indexOf("good"));
if(str.contains("boy"))
System.out.println("boy string found");
4. Close resources always
Whenever we open any file, dbConnection, scanner object etc. Remember to close the resource post performing all operations. This leads to memory leak if not closed. Also sometimes we write the code to close however during execution it abends due to exception and the resource is left open. To handle the scenario we need to use finally block. So it gets executed every time, whether exception occurs or doesn’t occur.
Scanner sc= new Scanner(System.in);
int res;
try{
System.out.println("Enter a number to divide with");
int a=sc.nextInt();
res=3/a;
System.out.println("Result"+res);
}
catch(Exception e){
e.printStackTrace();
}
finally{
//This block is called always and closes scanner object
sc.close();
System.out.println("scanner object closed");
}
5. Don’t create unnecessary objects/variables
Whenever we create any object or variable then it occupies space in memory. So create them only if it is required. Before creating an object first check, if the class object is null.
Employee emp;
if(emp==null){
Employee=new Employee();
else return emp;
/**********************************************/
String name="Biswajit Sundara";
boolean flag=name.isEmpty();
if(flag!=true){
System.out.println("My name= " +name);
}
/**********************can be written ************/
if(!name.isEmpty())
System.out.println("My name= " +name);
6. Read files efficiently
When we read files, if we read the whole file at once then the memory is consumed more. Let’s say we have huge file of 2 GB size. If we read the whole file at once then 2GB memory is occupied. Chances are more that we might encounter outofmemory exception if the system configuration doesn’t have more memory allocation. So its always recommendable to read line by line. It is memory efficient.
//Using Apache Commons POI
FileUtils.readLines(new File(path)); //occupies full memory so avoid this
//Streaming through the class file is memory efficient
FileInputStream inputStream = null;
Scanner sc = null;
try {
inputStream = new FileInputStream(path);
sc = new Scanner(inputStream, "UTF-8");
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(line);
}
// note that Scanner suppresses exceptions
if (sc.ioException() != null) {
throw sc.ioException();
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (sc != null) {
sc.close();
}
}
//Apache commons IO is also memory efficient
LineIterator it = FileUtils.lineIterator(theFile, "UTF-8");
try {
while (it.hasNext()) {
String line = it.nextLine();
// do something with line
}
} finally {
LineIterator.closeQuietly(it);
}
7. Follow 10-50-500 Rule
- 10: Package shouldn’t have more than 10 classes.
- 50: Method shouldn’t have more than 50 lines of code.
- 500: Class shouldn’t have more than 500 lines of code.
8. Apply SOLID design principles
- Single responsibility principle – A class should have one task to perform. If it is performing two tasks then it will create confusion. For example if a class is reading and writing data then split it so one class’s responsibility should be to read and another class’s responsibility to write data.
- Open/Closed principle – Open for extension and closed for modification. Developers should always extend the classes and then make modifications instead of modifying the existing classes.
- Liskov substitution principle – Use interfaces to create objects for implementing classes instead of creating objects of the classes directly.
- Interface segregation principle – It’s like Single Responsibility Principle but applicable to interfaces. Each interface should be responsible for a specific task. The developers need not to implement methods which he/she doesn’t need.
- Dependency inversion principle – Depend upon Abstractions- but not on concretions. This means that each module should be separated from other using an abstract layer which binds them together.
9. Remove boiler plate code
Boiler plate code is the code that have to be included in many places with little or no modifications. As a developer we can’t be redundant.
- Avoid multiple if/else blocks. Choose better alternatives
String value = object != null ? object.getValue() : null;
The above one line can be used instead of below 4 line of codes.
String value = null;
if(object != null) {
value = object.getValue();
}
- Use the object oriented concepts like inheritance, composition, interface, reflection etc to get high degree of code reusability.
- Remove the duplicate code blocks, create generic methods, classes to generate codes with different arguments.
10. Write clean code
- Follow naming conventions for variable, methods, class names etc. https://www.oracle.com/technetwork/java/codeconventions-135099.html
- Add comments properly. Usually I have seen people write comments about what is the code but the comments should also include why the code is written.
- Set the access specifiers properly. Public/Private/Protected as per the design. Final/Static/ Super key words should be used to control the manipulation.
- Don’t hard code any value. Read it from the external files. This way it is easy to maintain configuration changes without touching the codes.
- Write readable code, when someone reads your code it should be like a story, like a flow without disruption. Don’t write big line of code so that someone needs to scroll a lot. Better break it down.
- Avoid creating custom annotations for your project unless you are a damn good programmer, because its difficult to understand custom annotations created by someone and its hard to debug also since the error will be in run time which could have been addressed in compile time.
- Don’t forget to provide documentation. Use JavaDoc feature.
- Don’t keep variables, code blocks which are unused, unreached. Remove code blocks those are commented also.
- IDEs usually throw warnings. Make sure to review and correct all the warnings in compile time itself. It costs more if anything goes wrong in run time.
- Follow consistent writing pattern. If you are writing code in a certain way then maintain it through out the project. Everyone should follow that pattern too.
- Don’t open and close files/db too many times, it costs processing time. Instead of reading cell values from excel one by one opening and closing file again and again. We can open the excel file once, read the entire row, store it in a map and then close the file.
- Don’t write empty catch blocks. It means the code is silent on exceptions, it shouldn’t be.
- Log the errors, execution steps in a log file so that it’s easy to debug if the application encounters any problem.
- The code should follow the framework design pattern. Avoid creating your custom logic in your class files. For example in selenium automation framework, if we have a click method in the library use that, don’t use your own approach in the class file. If you have a better approach, reach out to the architect and change the library method itself.
11. Use Strings carefully
If two Strings are concatenated using “+” operator in a “for” loop, then it creates a new String Object, every time. This causes wastage of memory and increases performance time. Also, while instantiating a String Object, constructors should be avoided and instantiation should happen directly. For example:
//Slower Instantiation
String bad = new String("New string object");
//Faster Instantiation
String good = "New string object"
That’s it for now. If you are aware of any other coding best practices in Java, then do comment below. Thanks for reading!