Java Collections Framework

In this article, we are going to understand Java collections and its various implementations like List, Queue, and Set. We will also cover Java Maps and its implementations. We will also see the benefits of the collection. Last, we will cover the collection improvement in Java 8, and cover a bit about Stream API.

Java Collections- Introduction

In Java, a collection is a group of objects, it can take different implementations based upon the requirement, as a Set will not allow a duplicate element, or a TreeMap will keep its elements sorted in the natural order. The collection framework was introduced in Java2 or 1.2. The Collection framework allows storage and manipulation of a group of elements. It gives a flexible and amazing set of APIs to work on the elements. They allow the developer to insert, remove, search, and sort elements.

High-level diagram showing collection hierarchy:

The Java collection framework is part of the java.util package and it has a collection interface on the top of the hierarchy. We can see the complete hierarchy below, List, Queue, and Set implements the Collection interface. The Map interface is not inherited from the Collection interface and unlike List, Queue, or Set, it operates on key/value pair.

Collection in Java
Java Collections Hierarchy
Java collections framework
Java Collection Map Hierarchy

1. Java Collections Main Entities

There are four interfaces in Java Collection Framework, and they are List, Queue, Set, and Map.

1.1. List

The List stores a sequence of elements. We can access any element by its position in the List. The List interface has three implementing classes; ArrayListVector, and LinkedList.

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

1.2. Queue

The Queue maintains the order of the elements; it follows FIFO (first in, first out) concept. The Queue interface has two implementing classes; LinkedList and PriorityQueue

PriorityQueue<Integer> exampleQueue = new PriorityQueue<Integer>();

1.3. Set

The set is a collection of unique elements, and it doesn’t allow duplicate elements. The classes HashSet and LinkedHashSet implements this interface, and extended by SortedSet class extends this interface. TreeSet implements SortedSet.

Set<Integer> exampleSet = new HashSet<Integer>();

1.4. Map

The Map interface is not inherited from the Collection interface. It is a collection of elements as key/value pair. The classes HashMap and LinkedHashMap implements this interface, and extended by SortedMap class extends this interface. TreeMap implements SortedMap.

Map<String, String> exampleMap = new HashMap<String, String>();

2. Java Collections Benefits

Below are the major benefits of using java collection classes.

  • Java collections provide various mechanisms to store and manipulate objects. It gives various methods to developers by default and saves a lot of time for them.
  • Developers are no longer required to write custom collections APIs as they get these APIs by these collections.
  • Java Collections can grow and resize dynamically.
  • Different collections provide solutions to various business problems and help developers to achieve the best possible time and space complexity.
  • They are well written and bug-free.
  • They handle the boundary cases and exceptions, which a normal developer can easily miss.

3. Collections Framework and equals and hashcode

While working with the Collection framework, we must override two java Object’s methods; equals() and hashCode() according to their general contract. Please note that the default implementation will always be there for these methods however, as per the situation, we must override them. The hashcode() returns an integer value (unique identifier) for every object, and the equals() method checks the equality of the objects and it returns true or false accordingly.

#<strong>hashCode()/equals() method signature:</strong>
public int hashCode()
public boolean equals(Object obj)

3.1. hasCode() General Contract

  • If the hashcode() method is called upon the same object multiple times, it should return the same hashcode (provided the object hasn’t changed).
  • If the equals() method returns true for two objects, then their hashcode should be the same as well returned by the hashCode() method.
  • It is unnecessary that the hashCode() method will return the different hashcodes for the objects that are not equal as per equals() method.

3.2. equals() General Contract

For any non-null variables, x, y and z

  • x.equals(x) shall always true.
  • x.equals(y) is true only when <strong>y.equals(x)</strong> is also true.
  • If x.equals(y) is true and y.equals(z) is also true then z.equals(z) must also be true.
  • x.equals(y) should always return <strong>true</strong>/<strong>false</strong> if the value of the objects is not changed.
  • x.equals(null) returns false.

So it is necessary to override the hashCode() method of the Object class if we are overriding the equals() method.

4. Java 8 and Collections Improvements

There are some improvements made for collection classes in java 8 and otherwise in language as well. Here are the important ones:

  • Lambda Expressions -> Most of the Java collection classes have been updated to use Lambda expression.
  • Usage of forEach() for iteration -> Most of the Java collection classes have been updated to use the forEach() method.
  • HashMap getOrDefault() method -> instead of boring if else, we can use getOrDefault() method which will return the which a key is mapped or return a default value if it doesn’t exist in the map.
Map < Integer, Integer > map = new HashMap < > ();
map.put("1", "100");
String val = map.getOrDefault("2", "not present!");
System.out.println(val); // will print not present!
  • HashMap putIfAbsent() -> Puts an entry if the key isn’t present.
  • HashMap Key Collisions -> Use of the red-black tree instead of a LinkedList.
  • Streams Lazy Evaluation -> Evaluation happens only when terminal operations are called on stream.
  • Support for Parallelism -> The new modification on the Java 8 Collections API has operations that have in-built support for parallelism

5. Java 8 Stream API and Collections Framework

The Java 8 Stream classes support the functional-style operations on a collection of elements, they are wrappers around the collections. It allows the bulk operations easy, and convenient. The classes are under java.util package. Please note that a stream will never store the data, it will never change the collection’s data as well. It is lazy; the actual computation doesn’t happen until we use terminal operations (e.g. forEach(), reduce(), collect()) on the source. Let us go over the basic operations and usage of Stream classes:

  • forEach() -> This method loops over the stream elements and calls the function on each element of the stream. We widely used it inside java libraries.
  • map() -> This method applies a function on each element and produces a fresh stream, the type of new stream can be different. For example, from a stream of numbers, square each number and print.
List < Integer > list = Arrays.asList(2, 4, 6, 8, 10);
list.stream().map(n - > n * 2).forEach(System.out::println);
  • collect() -> This is a terminal operation that performs a mutable reduction operation on the elements of the stream.
  • filter() -> This operation is helpful when we want to filter out elements of a stream based on some condition. It produces a new stream. Below is an example of finding even numbers and printing them.
List < Integer > list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.stream().filter(num - > num % 2 == 0).forEach(System.out::println);
  • toArray() -> This method will return an array of elements from the stream
Stream < Integer > list = Stream.of(1, 2, 3, 4, 5);
Object[] array = list.toArray();
System.out.println(Arrays.toString(array));

6. Generics and Java Collections Framework

Generics have been introduced in Java 5. It provides type-safe collections, and type gets checked at compile time itself. It also removes/disables the use of type-casting. It allows the data type to be passed as a parameter to collection classes. In the end, the Java compiler will check if the type is correct or not. Generics allow a single type of object as part of the collection class. We can apply generics at any level, like Interfaces, Classes, or even methods.

Generic Interface Syntax
public interface List<E> extends Collection<E>

Generic Class Syntax
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

Generic Method Syntax
public boolean add(E e)

The other key aspects of generics are:

  • Bounded Type Parameters: It is to restrict the types we can use as a type argument in a parametrised type.
  • Type Interface: It allows us to invoke a generic method like a normal method without specifying the type in angular brackets.
  • Wildcards: In generics, “?” is known as wildcard, which means unknown type.
  • Type Erasure: Replace parametrised type with an object by the compiler

Summary

In this article, we introduced the Java Collections framework and following important points:

  • In this article, we have understood the basics of Java Collections.
  • We have understood the hierarchy of Java Collections.
  • The hierarchy of Java Map Interface.
  • The importance of equals() and hashCode() methods in collection classes.
  • Understood the Stream APIs.
  • We have understood the changes made to Java Collections in Java 8 release.
  • In this article, we have understood the concept of Generics and how it is used in Java collections.

You can download the source code for all articles published on our site from our GitHub repository.