DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Sonia Mathias
Sonia Mathias

Posted on

Java Collections - Best Practices

Introduction

Java Collections are very popular and you might have heard of them if you have ever done programming in Java. If you are a Java programmer, you would have probably used the Java collections many times in your code. The Collection Framework is a Java framework that has many Interfaces in it that can be used to create pre-written data structures to ease our work. These data structures that we use in our code are called Java Collections. For instance, ArrayList, LinkedList, HashMap, HashSet, TreeSet, etc. are all Java Collections and a part of the Collection framework.
So, in this article, we are going to discuss some of the best practices that you can apply while using Java Collections in your code.

Java Collections Best Practices

Why is it important to know about the best practices? This is a very important question to address. Many programmers usually think that whatever they write is working so, why is there a need to learn some other way of doing it. The answer is very simple. Following the best practices makes the code clean, efficient, and more readable and also enhances one's skills as a software developer/problem solver. So, let us see what are some of the best practices while using Java Collections.

1. Always try to use an Interface instead of a Class while declaring any Collection, for Method Arguments, and even while returning a Collection from any method.

This is a very basic point that many people lack while they are writing Java programs. Let us understand this with the help of some examples. Let us say we want to have an ArrayList of Integer in our code for performing some functions. The usual practice that many people follow while declaring an ArrayList is

ArrayList<Integer> list = new ArrayList<>();

However, this is not the best practice. The best practice for declaring a Collection is declaring it using the Interface as shown below:

List<Integer> list = new ArrayList<>();

Now, the question is why? Why should we declare it using an interface? The reason is simple. If we use Interface, it provides more functionality. If you have declared a List, you can later change it from ArrayList to LinkedList any time you want to. Similarly, if you declare a Set, we can use it as both HashSet and TreeSet. So, using an interface provides more flexibility in the code and hence we should always try to use an interface instead of a class.

So, we should use an interface instead of a Class. However, this is not just valid while declaring the collection but also while passing an argument to any method or even returning a collection from the method.

For example, let us say we have a list of input numbers and we want to select only odd numbers from them. A program for the same is shown below

import java.util.*;
import java.io.*;
class Main {

    public static List<Integer> selectOdds(List<Integer> numbers) {

         List<Integer> odd = new ArrayList<>();

         for(int no : numbers) {
             if(no % 2 != 0) {
                 odd.add(no);
             }
         }

         return odd;
    }

    public static void main(String args[]) throws Exception{
        Scanner scn = new Scanner(System.in);
        int n = scn.nextInt();
        List<Integer> list = new ArrayList<>();
        while(n-- > 0) {
            int x = scn.nextInt();
            list.add(x);
        }

        System.out.println("The list of odd numbers is");
        System.out.println(selectOdds(list));
    }
}

Enter fullscreen mode Exit fullscreen mode

Output:Code Link

Note: For Code Implementation- Link

**Code Explanation: **In the above code, we take an input number N. Then, we input N numbers that are stored in the list. Then, we call our selectOdds() method which returns a list of all odd numbers out of the complete list.

We have followed the best practice of using Interface instead of a class here as well. We have used List instead of ArrayList or LinkedList for both, as a return type as well as the argument of the method.

2. Always try to use isEmpty() for a collection over size() while checking for the emptiness of a Collection.

We often use Collections for storing some data in our programs. We might also try to empty the collection or check for its emptiness for performing some operations. In Java, it is always advised to use the isEmpty() method to check whether the collection is empty or not rather than checking whether the size is greater than zero or not. For instance, let us look at the following code.

import java.util.*;
class Main {
    public static void main(String args[]) {

        Scanner scn = new Scanner(System.in);
        int n = scn.nextInt();

        List<Integer> list = new ArrayList<>();

        while(n-- > 0) {
            int x = scn.nextInt();
            list.add(x);
        }

        //Less readable method for 
       //checking the emptiness of a Collection
        while(list.size() > 0) {
            System.out.print(list.remove(0) + " ");
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Code Link: Output

**Code Explanation: **In the above code, we take an integer N as the input and then we input N values and add these values to an ArrayList. Now, till the ArrayList becomes empty, we keep on removing the element at index 0 from the ArrayList.

So, instead of using the method shown above, we should use isEmpty() as shown below:

import java.util.*;
class Main {
    public static void main(String args[]) {

        Scanner scn = new Scanner(System.in);
        int n = scn.nextInt();

        List<Integer> list = new ArrayList<>();

        while(n-- > 0) {
            int x = scn.nextInt();
            list.add(x);
        }

        //More readable method for 
    //checking the emptiness of a Collection
        while(!list.isEmpty()) {
            System.out.print(list.remove(0) + " ");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Code Link: Output

Code Explanation: **We have done the same thing that we did in the previous program. The only difference is that we use isEmpty() instead of using the size() method.
**Is there a difference in the Time Complexities of size() and isEmpty()?

No, both these methods are the same performance-wise. The difference is in the readability of the code. Using the isEmpty() method increases the readability and hence it is preferred over the size() method and is considered a good practice.

3. Always return Empty Collections rather than returning null.

We encounter such methods in Java in the everyday practice of programming where we have to return some Collection from a method. We should always return an empty Collection rather than returning null.

The reason for this is that when we write APIs in Java or write the Java programs that interact with APIs, it can cause non-deterministic errors. Also, the performance of returning a null or an empty Collection is the same. So, we should return an empty Collection. This can be done in 2 ways. Let us say, we have a function where we have to return an ArrayList. So, one method of doing this can be:

return new List<>();

Another way to do this is:

return Collections.empty();

4. Try not to use the traditional For-Loop

For iterating over a Collection, you can either use the traditional for loop as shown below:

for(int i=0;i<list.size();i++) {

     System.out.println(list.get(i));

}
Enter fullscreen mode Exit fullscreen mode

Here, β€œlist” is an ArrayList Collection. Most people use the traditional for-loops only and there is nothing wrong with using them. However, it is preferred to use either an Iterator or a for-each loop instead of a traditional for-loop.

*Traversal using Iterator *

import java.util.*;
class Main {
    public static void main(String args[]) {
        // Your code goes here
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        Iterator<Integer> it = list.iterator();

        while(it.hasNext()) {
            System.out.print(it.next() + " ");
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Code Link: Output

Code Explanation: In the above code, we have entered the value 1,2 and 3 into a List. Then, we created an Iterator object for traversing the list. The hasNext() method is also a part of the predefined Java methods for traversing the Collections. Using the hasNext() we have traversed the Collection List and printed the value that were inside it.

Now, let us see how we can traverse a Collection using for-each loop.

import java.util.*;
class Main {
    public static void main(String args[]) {
        // Your code goes here
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        for(Integer a : list) {
            System.out.print(a + " ");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Code Link: Output

Advantage of Using For-Each Loop: There are multiple advantages of using a for-each loop. They are easy to write and understand and increase the readability of the code. Apart from that, using an iterator or a for-each loop may throw an exception called ConcurrentModificationException. This exception takes place when we are traversing a Collection and trying to modify it at the same time in Java. Hence, using for-each loop is more secure as well as it can save us from big blunders in our program.

5. If possible, mention the capacity of a Collection.

It is considered a good practice to mention the initial capacity of a Collection. Let us say that we have ArrayList in our program. We know that the ArrayList grows its size dynamically. Now, if we know that we are not going to store more than 10000 numbers in the ArrayList prior to writing the code, we should specify the capacity of the ArrayList to be 10000. This is because if the size is not specified, the ArrayList dynamically growing its size might grow its size even more than 10000. So, this is just a waste of memory. The size can be specified while declaring a Collection as follows:

List<Integer> list = new ArrayList<>(10000);

Since we have mentioned the capacity of the collection, now it will not grow more than 10000 till you cross the 10000 mark of elements.

What will happen if we insert more elements after reaching the Capacity Limit set by us in the beginning?

So, from the beginning i.e. when the ArrayList is empty, till you reach 10000 elements, the capacity of the ArrayList remains 10000 only. Now, let us say you cross 10000 elements, now the Capacity will again grow dynamically and you do not have any control over it again.

*6. Using the Correct Collections. *

Finally, after discussing a lot of best practices that one should follow while using the Java Collections, here we are at the most important practice. Try to use the correct Collection always. What do mean by this? Let us understand this with the help of an example.

Let us say that we want to store only unique values and the order of the values is not of concern to us. Then, we can go for HashSet Collection which serves the purpose of storing the unique elements only. We should not use a HashMap here as we only want to store some unique values and not some key-value pairs. Also, we should not use ArrayLists as they cannot store unique values only. In ArrayLists, duplicates are allowed.

In the true sense, choosing the correct Collection seems to be an art. However, it is not. You just need to follow some rules in order to select the correct Java Collection. Some of the key points based on which you can select a Collection are as follows:

Storing Values or Key-Value Pairs: You need to be sure of what you want to store. If you want to store only values, then HashMap and TreeMap, and LinkedHashMap are not the Collections that you should go for, rather you should go for ArrayList, LinkedList, HashSet, etc.

Unique or Duplicate Values: Again, you need to be sure whether you are going to store some unique values or duplicate ones. For instance, storing the names of Students will be storing duplicate data only as the names can be the same. However, if we choose to store enrollment numbers of these students, the Collection used for this should be the one that stores only unique values as Enrollment Numbers of 2 students are never the same.

Does Order Matter or not: Again, a very important question to address is whether the order of the data that you are searching for/storing matters or not. For example, if you just want to store your data and want fast searching, you can use an unordered Collection for this however, if you want the order to be maintained, you will have to choose an ordered collection.

So, keeping these points in mind can help you choose the right Collection. The flowchart below is the correct way of making the decision on which collection to use:

Flow Chart

The above flowchart is the complete guide to deciding on the Best Collection.

So, these were some of the best practices that one should follow while using Java Collections.

Conclusion:

So, we have studied some of the best practices that Java programmers and developers can follow while using the Java Collections. It is very important to understand that these practices are for you so that you can become a better programmer. The other practices might run your program correctly, but do reflect you as the best of the programmers. Hence, it is advised to follow them.

Top comments (0)

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.