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.
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; ArrayList, Vector, 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 samehashcode
(provided the object hasn’t changed). - If the
equals()
method returns true for two objects, then theirhashcode
should be the same as well returned by thehashCode()
method. - It is unnecessary that the
hashCode()
method will return the differenthashcodes
for the objects that are not equal as perequals()
method.
3.2. equals() General Contract
For any non-null variables, x, y and z
x.equals(x)
shall alwaystrue
.x.equals(y)
istrue
only when<strong>y.equals(x)</strong>
is alsotrue
.- If
x.equals(y)
istrue
andy.equals(z)
is alsotrue
thenz.equals(z)
must also betrue
. x.equals(y)
should always return<strong>true</strong>/<strong>false</strong>
if the value of the objects is not changed.x.equals(null)
returnsfalse
.
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 usegetOrDefault()
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 aLinkedList
. - 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()
andhashCode()
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.