Tuesday, 13 April 2021

Java Collection Framework Best Practices

       In this article we can discuss the best practices of java collection and sample examples for each usages.

1) Choose the right collection

        This is the most important step before using any collection. Depending upon functionality or problem solve to decide the right collection to use in the code implementation. There are multiple criteria to choose right collection - 

1) collection allows duplicates or not 

2) accepts null or not 

3) search based on index

4) Supports concurrency.

and also the developer should know the performance impact on each collection usages.


2) Use interface type when declaring any collection.

When you declare or create any collection, prefer to use interface type instead of specific collection class.

Example:-

List<String> listOfItems = new ArrayList<>();

instead of,

ArrayList<String> listOfItems = new ArrayList<>();

Using interface type is more flexible and convenient because we can change concreate implementation as needed.

For example,  List<String> listOfItems = new LinkedList<>();


3) Method return type should be interface instead of collection class.

       Best practice to return type of method should be interface instead of collection class. It is more flexible because if any changes done inside the method should not impact on the caller.

 public List<String> getAllItems() {
      
      List<String> listOfItems = new ArrayList<>();
      
      //in future if we change arrayList to linkedList it doesn't impact on caller
      // get all items - fetch items from database

      return listOfItems;
 }

4) Use generic type and diamond operator

        Use generic type when declare any collection otherwise chances of throwing ClassCastException at run time.

Example : - 

List listOfItems = new ArrayList<>();

listOfItems.add("item");

listOfItems.add(1);

so avoid using above declaration, apply generics,

List<String> listOfItems = new ArrayList<>();

listOfItems.add("company");

listOfItems.add(12);     //compile time error

 Use diamond operator(<>),

This operator <> is called the diamond operator. Without diamond operator we have to write declaration twice so using this operator no need to write declaration twice as follows,

List<String> listOfItems = new ArrayList<String>();

With <> operator,

List<String> listOfItems = new ArrayList<>();


5) Prefer to use Collection isEmpty() or CollectionUtils.isEmpty()  methods instead of size() method

Checking the emptiness of collection, avoid using size method like,

if  (listOfItems.size > 0)  {
   //write logic if list is not empty
}

Prefer to use collection or collection util methods,

if  (!listOfItems.isEmpty())  {
    //write logic if list is not empty
}

or use collection util(apache) method,

if (CollectionUtils.isNotEmpty(listOfItems)) {
      //write logic if list is not empty
}


6) Specify initial capacity of a collection if possible

        When we process a batch of records then prefer to specify the collection initial capacity as a batch size so it avoids every time to resize the collection capacity when it exceeds the default capacity.

List<String> listOfItems = new ArrayList<>(500);

This creates an array list of 500 elements initially.

7)  Return an empty array or collection instead of a null value for methods that return an array or collection

       If the method is collection valued then return empty collection instead of returning null because the client side no need to handle null check it's kind of extra
code to write in the client side. 

//Avoid this
public List<String> getAllPreSaleItems(String itemType) {
		
	List<String> preSaleItems = null;
	if (itemType.equalsIgnoreCase("preSale")) {
		//preSaleItems = fetch all presale items from database
	}
	return preSaleItems;
}

Best practice to return empty list as, 

//best practice to return empty list
private List<String> getAllPreSaleItems(String itemType) {
		
	List<String> preSaleItems = null;
	if (itemType.equalsIgnoreCase("preSale")) {
		//preSaleItems = fetch all presale items from database
	}
	if (preSaleItems == null) {
		return Collections.EMPTY_LIST;
	}
	return preSaleItems;
}


8) Do not use the classic for loop

Classic for loop,

List<String> listOfItems = Arrays.asList("a", "b", "c");
for (int i=0; i<listOfItems.size(); i++) {
   System.out.println(listOfItems(i));
}

Better to use for-each loop,

List<String> listOfItems = Arrays.asList("a", "b", "c");
for (String item : listOfItems) {
   System.out.println(item);
}

The advantage of the for-each loop is that it eliminates the possibility of bugs and makes the code more readable. It traverses each element one by one. 

9) Prefer to use forEach() with Lambda expressions(Java 8 onwards)

       In forEach() method use java 8 lambda expression,  so that the code even more compact, more flexible and more powerful.

Example:-

List<String> skuList = Arrays.asList("MTCCC", "MTAAA", "PT1111");
 
skuList.forEach(sku -> System.out.println(sku));


10) Prefer concurrent collections over synchronized wrappers

          When we develop any multi-threaded applications,  consider using concurrent collections in the java.util.concurrent package instead of using the synchronized 
collections generated by the Collections.synchronizedXXX() methods.
         It’s because the concurrent collections are designed to provide maximum performance in concurrent applications,  by implementing different synchronization mechanisms like copy-on-write, compare-and-swap and special locks. 
 
Example of concurrent package classes are ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentSkipListMap and PriorityBlockingQueue.


Related Posts:-

1 comment: