📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
1. Code for Interface, not for Implementation
1. Always use interface type as a reference type.
For example, use List, Set, Map, 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<>();
List<String> list = new LinkedList<>();
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) {
}
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)));
}
}
String opetation with + operator took [86037] nano seconds
String opetation with StringBuilder took [7928] nano seconds
String opetation with StringBuffer took [14165] nano seconds
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.- use == to compare primitive e.g. boolean, int, char, etc, while use equals() to compare objects in Java.
- == return true if two references are of 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.
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));
}
}
== 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
2. Classes naming conventions
3. Interfaces naming conventions
4. Methods naming conventions
5. Variables naming conventions
6. Constants naming conventions
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;
}
List<Student> listStudents = new ArrayList<>;
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
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());
}
}
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());
}
}
}
}
try(// open resources here){
// use resources
} catch (FileNotFoundException e) {
// exception handling
}
// resources are closed as soon as try-catch block is executed.
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();
}
}
- 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();
}
7. Prefer switch( ) Statement Instead of Multiple if-else-if
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;
}
}
}
8. Use enums instead of int constants
What is a problem with using int constants?
// 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;
- 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)
What is the solution?
public enum Apple {
FUJI, PIPPIN, GRANNY_SMITH
}
public enum Orange {
NAVEL, TEMPLE, BLOOD
}
- 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
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();
}
}
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();
}
}
}
- 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.
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));
for (String fruit : fruits) {
System.out.println(fruit);
}
Comments
Post a Comment
Leave Comment