In this article “Improvements in Java Collection API” we will learn about the origin of classes in different versions of jdk. Also, If any new class or interface introduced, what was the purpose of introducing that? What was the weakness of previous version of it? Why the new class/interface introduced? What all new features introduced in Collection classes/interfaces? and so on. We are going to discuss about these kinds of questions in this article accordingly. In fact, we will learn about Enhancement of java collection classes step by step & also version by version simultaneously. Primarily, we will focus on Java collection features in JDK 1.0, 1.2, 1.4, 1.5, 1.6 and 1.8, 9 10, 11, 21 respectively.
Table of Contents
Improvements in Java Collection API
Collection classes in JDK 1.0
Collection classes in JDK 1.2
Collection classes in JDK 1.4
Collection classes in JDK 1.5
Collection classes in JDK 1.6
Collection classes in JDK 1.8
Collection Enhancements in Java 9
Collection Enhancements in Java 10
Collection Enhancements in Java 11
Collection Enhancements in Java 21
SequencedCollection
SequencedSet
SequencedMap
Improvements in Java Collection API
The Java Collection API has evolved significant improvements over the years, introducing new features, enhancements, and optimizations to enhance developer’s productivity, improve performance, and adapt to revised programming pattern and requirements. It will help developers to build more robust, efficient, and maintainable applications using the power of collections in Java.
Collection classes in JDK 1.0
In JDK 1.0 there were four classes Vector, Stack, Hashtable & Properties. In addition, there was one interface ‘Enumeration’ to iterate values in easy way. Further classification, Stack is a subclass of Vector & Properties is a subclass of Hashtable.
Problems with Vector class :
- Vector is a thread-safe ie. All methods in the Vector are synchronized. So it is not good for single-threaded environment.
- Since it works internally on arrays concept, insertion & deletion operations are very slow.
- Even it allows adding duplicate elements in it.
- Additionally, Vector can’t store elements in sorted order.
Problems with Hashtable class :
=>Hashtable is a thread-safe ie. all methods in Hashtable are synchronized. So it is not good for single-threaded environment.
=>Also, Hashtable can’t store entries in sorted order.
Problems with Enumeration:
=> It can’t delete elements & method names are large.
Collection classes in JDK 1.2
In JDK 1.2 Sun Micro-system had introduced ArrayList, LinkedList, HashSet, TreeSet, HashMap,TreeMap, Iterator & ListIterator.
ArrayList: Introduced to provide solution of single threaded environment as methods in ArrayList are not synchronized. Java ArrayList is a part of the Java collections framework and it is a class of java.util package. It provides us with dynamic arrays in Java. Though, it may be slower than standard arrays but can be helpful in programs where lots of manipulation in the array is needed. This class is found in java.util package. The main advantage of ArrayList in Java is, that if we declare an array then we need to mention the size, but in ArrayList, it is not needed to mention the size of ArrayList. If you want to mention the size then you can do it.
LinkedList: Introduced to provide faster insertion & deletion of elements. Linked List is a part of the Collection framework present in java.util package. This class is an implementation of the LinkedList data structure which is a linear data structure where the elements are not stored in contiguous locations and every element is a separate object with a data part and address part. The elements are linked using pointers and addresses. Each element is known as a node.
The LinkedList is a versatile data structure, especially useful when you need constant-time insertions and deletions. If you’re looking to master LinkedList and other key Java collections,
Example:
HashSet: Introduced to provide solution of duplicate elements as there are no duplicates are allowed in HashSet. Java HashSet class implements the Set interface, backed by a hash table which is actually a HashMap instance. No guarantee is made as to the iteration order of the hash sets which means that the class does not guarantee the constant order of elements over time. This class permits the null element. The class also offers constant time performance for the basic operations like add, remove, contains, and size assuming the hash function disperses the elements properly among the buckets,
Java HashSet Features
- A few important features of HashSet are mentioned below:
- Implements Set Interface.
- The underlying data structure for HashSet is Hashtable.
- As it implements the Set Interface, duplicate values are not allowed.
- Objects that you insert in HashSet are not guaranteed to be inserted in the same order. Objects are inserted based on their hash code.
- NULL elements are allowed in HashSet.
- HashSet also implements Serializable and Cloneable interfaces.
Example:
TreeSet: Introduced to store elements in sorted order. TreeSet is one of the most important implementations of the SortedSet interface in Java that uses a Tree for storage. The ordering of the elements is maintained by a set using their natural ordering whether or not an explicit comparator is provided. This must be consistent with equals if it is to correctly implement the Set interface.
It can also be ordered by a Comparator provided at set creation time, depending on which constructor is used. The TreeSet implements a NavigableSet interface by inheriting AbstractSet class.
Example:
HashMap: Introduced to provide solution of single threaded environment as methods in HashMap are not synchronized. In Java, HashMap is a part of Java’s collection since Java 1.2. This class is found in java.util package. It provides the basic implementation of the Map interface of Java. HashMap in Java stores the data in (Key, Value) pairs, and you can access them by an index of another type (e.g. an Integer). One object is used as a key (index) to another object (value). If you try to insert the duplicate key in HashMap, it will replace the element of the corresponding key.
What is HashMap?
Java HashMap is similar to HashTable, but it is unsynchronized. It allows to store the null keys as well, but there should be only one null key object and there can be any number of null values. This class makes no guarantees as to the order of the map. To use this class and its methods, you need to import java.util.HashMap package or its superclass
Example:
TreeMap: Introduced to store elements in sorted order. The TreeMap in Java is used to implement Map interface and NavigableMap along with the AbstractMap Class. The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used. This proves to be an efficient way of sorting and storing the key-value pairs. The storing order maintained by the treemap must be consistent with equals just like any other sorted map, irrespective of the explicit comparators. The treemap implementation is not synchronized in the sense that if a map is accessed by multiple threads, concurrently and at least one of the threads modifies the map structurally, it must be synchronized externally.
The TreeMap in Java is a concrete implementation of the java.util.SortedMap interface. It provides an ordered collection of key-value pairs, where the keys are ordered based on their natural order or a custom Comparator passed to the constructor.
A TreeMap is implemented using a Red-Black tree, which is a type of self-balancing binary search tree. This provides efficient performance for common operations such as adding, removing, and retrieving elements, with an average time complexity of O(log n).
Example:
Iterator: Introduced to solve problems of Enumeration. Also has one class ListIterator to deal with List specially. In Java, an Iterator is one of the Java cursors. Java Iterator is an interface that is practiced in order to iterate over a collection of Java object components entirety one by one. It is free to use in the Java programming language since the Java 1.2 Collection framework. It belongs to java.util package.
Though Java Iterator was introduced in Java 1.2, however, it is still not the oldest tool available to traverse through the elements of the Collection object. The oldest Iterator in the Java programming language is the Enumerator predated Iterator. Java Iterator interface succeeds the enumerator iterator that was practiced in the beginning to traverse over some accessible collections like the ArrayLists.
Example:
Problems with HashSet: It can’t maintain the insertion order ie. It doesn’t store the elements in the order they have added to the collection.
Problems with HashMap: Like HashSet, It can’t maintain insertion order.
Collection classes in JDK 1.4
In JDK 1.2 Sun Micro-system had introduced LinkedHashSet & LinkedHashMap.
LinkedHashSet: Introduced to solve the problem of insertion order in HashSet. It stores the elements in the order they have added to the collection. The LinkedHashSet is an ordered version of HashSet that maintains a doubly-linked List across all elements. When the iteration order is needed to be maintained this class is used. When iterating through a HashSet the order is unpredictable, while a LinkedHashSet lets us iterate through the elements in the order in which they were inserted. When cycling through LinkedHashSet using an iterator, the elements will be returned in the order in which they were inserted
Example:
LinkedHashMap: Introduced to solve the problem of insertion order in HashMap. It also stores the elements in the order they have added to the collection. The LinkedHashMap Class is just like HashMap with an additional feature of maintaining an order of elements inserted into it. HashMap provided the advantage of quick insertion, search, and deletion but it never maintained the track and order of insertion, which the LinkedHashMap provides where the elements can be accessed in their insertion order.
Features of a LinkedHashMap:
Collection classes in JDK 1.5
Introduced Queue & its subclasses.
for-Each loop: as an alternative approach to iterate in place of Iterator
Queue: Queues are an essential data structure for managing tasks in a first-in-first-out manner. To explore their implementation in greater depth and see real-world examples, the Java Programming Course provides a comprehensive guide to queues and other data structures, helping you master them with practical projects
The Queue Interface is present in java.util package and extends the Collection interface is used to hold the elements about to be processed in FIFO(First In First Out) order. It is an ordered list of objects with its use limited to inserting elements at the end of the list and deleting elements from the start of the list, (i.e.), it follows the FIFO or the First-In-First-Out principle.
Example:
CopyOnWriteArrayList: Introduced to allow for the safe iteration over the elements, even if we modify the underlying list. CopyOnWriteArrayList class is introduced in JDK 1.5, which implements the List interface. It is an enhanced version of ArrayList in which all modifications (add, set, remove, etc) are implemented by making a fresh copy. It is found in java.util.concurrent package. It is a data structure created to be used in a concurrent environment.
- As the name indicates, CopyOnWriteArrayList creates a Cloned copy of underlying ArrayList, for every update operation at a certain point both will be synchronized automatically, which is taken care of by JVM. Therefore, there is no effect for threads that are performing read operation.
- It is costly to use because for every update operation a cloned copy will be created. Hence, CopyOnWriteArrayList is the best choice if our frequent operation is read operation.
- The underlined data structure is a grow-able array.
- It is a thread-safe version of ArrayList.
- Insertion is preserved, duplicates, null, and heterogeneous Objects are allowed.
- The main important point about CopyOnWriteArrayList is the Iterator of CopyOnWriteArrayList can not perform remove operation otherwise we get Run-time exception saying UnsupportedOperationException. add() and set() methods on CopyOnWriteArrayList iterator also throws UnsupportedOperationException. Also Iterator of CopyOnWriteArrayList will never throw ConcurrentModificationException.
Example:
CopyOnWriteArraySet: It uses an internal CopyOnWriteArrayList for all of its operations. So it shares the same basic properties of that. CopyOnWriteArraySet is a member of the Java Collections Framework. It is a Set that uses an internal CopyOnWriteArrayList for all of its operations. It was introduced in JDK 1.5, we can say that it is a thread-safe version of Set. To use this class, we need to import it from java.util.concurrent package.
It shares some properties of Set and also has its own properties as listed:
- The internal implementation of CopyOnWriteArraySet is CopyOnWriteArrayList only.
- Multiple Threads are able to perform update operations simultaneously but for every update operation, a separate cloned copy is created. As for every update a new cloned copy will be created which is costly. Hence if multiple update operations are required then it is not recommended to use CopyOnWriteArraySet.
- While one thread iterating the Set, other threads can perform updation, here we won’t get any runtime exception like ConcurrentModificationException.
- An Iterator of CopyOnWriteArraySet class can perform only read-only and should not perform the deletion, otherwise, we will get Run-time exception UnsupportedOperationException.
- Use CopyOnWriteArraySet in applications in which set sizes generally stay small, read-only operations vastly outnumber mutative operations, and you need to prevent interference among threads during traversal.
- CopyOnWriteArraySet helps in minimizing programmer-controlled synchronization steps and moving the control to inbuilt, well-tested APIs
Example:
image
Collection classes in JDK 1.6
NavigableSet: As a SortedSet extended with navigation methods reporting closest matches for given search targets. NavigableSet represents a navigable set in Java Collection Framework. The NavigableSet interface inherits from the SortedSet interface. It behaves like a SortedSet with the exception that we have navigation methods available in addition to the sorting mechanisms of the SortedSet.
For example, the NavigableSet interface can navigate the set in reverse order compared to the order defined in SortedSet. A NavigableSet may be accessed and traversed in either ascending or descending order. The classes that implement this interface are, TreeSet and ConcurrentSkipListSet
NavigableMap: As a SortedMap extended with navigation methods returning the closest matches for given search targets. The NavigableMap interface is a member of the Java Collection Framework. It belongs to java.util package and It is an extension of SortedMap which provides convenient navigation methods like lowerKey, floorKey, ceilingKey and higherKey, and along with this popular navigation method. It also provide ways to create a Sub Map from existing Map in Java e.g. headMap whose keys are less than the specified key, tailMap whose keys are greater than the specified key, and a subMap which strictly contains keys which fall between toKey and fromKey.
Example:
Collection classes in JDK 1.8
There are also new updates in the Java Collections Framework to support lambda expressions, streams, and aggregate operations.
♦ stream() as a default method in parent Interface Collection<E> : Returns a sequential Stream with this collection as its source. ntroduced in Java 8, Stream API is used to process collections of objects. A stream in Java is a sequence of objects that supports various methods that can be pipelined to produce the desired result. Java Stream Creation is one of the most basic steps before considering the functionalities of the Java Stream
Example:
♦ parallelStream() as a default method in parent Interface Collection<E> : Returns a possibly parallel Stream with this collection as its source. One of the prominent features of Java 8 (or higher) is Java Parallel Stream. It is meant for utilizing the various cores of the processor. Usually, any Java code that has only one processing stream, where it is sequentially executed. However, by using parallel streams, one can separate the Java code into more than one stream, which is executed in parallel on their separate cores, and the end result is the combination of the individual results. The order in which they are executed is not in our control. Hence, it is suggested to use a parallel stream when the order of execution of individual items does not affect the final result.
Example:
♣ spliterator() as a default method in parent Interface Collection<E> : Creates a Spliterator over the elements in this collection. The spliterator() method of ArrayList returns a Spliterator of the same elements as ArrayList but created Spliterator is late-binding and fail-fast. A late-binding Spliterator binds to the source of elements. It means that Arraylist at the point of the first traversal, first split, or the first query for estimated size, rather than at the time the Spliterator is created. It can be used with Streams in Java 8. Also, it can traverse elements individually and in bulk too. Spliterator is a better way to traverse over element because it provides more control over elements.
Example:
♣ removeIf(Predicate<? super E> filter) as a default method in parent Interface Collection<E> : Removes all of the elements of this collection that satisfy the given predicate.
Equally important, one noticeable point here is that all newly added methods are default methods inside interface Collection<E>. This is the best example of default methods usage. The removeIf() method of ArrayList is used to remove all of the elements of this ArrayList that satisfies a given predicate filter which is passed as a parameter to the method.
Errors or runtime exceptions are thrown during iteration or by the predicate are pass to the caller. This method returns True, if we are able to remove some element.
Java 8 has an important in-built functional interface which is Predicate. Predicate, or a condition checking function, check the given input for a given condition and returns a boolean result for the same indicating whether the condition was met or not.
Example:
Collection Enhancements in Java 9
1) Introduction of new of() static factory methods for creating immutable lists, sets, and maps. These methods are:
List.of(), Set.of(), Map.of(), Map.ofEntries()
2) Arrays.mismatch(): Added new method to find the index of first mismatch between two arrays. The mismatch() is a method that is defined under the Arrays class of Java.util package and it is used with respect to the two arrays passed as an argument in the mismatch method. This method returns the index at which two arrays passed as a parameter to the mismatch() function have the first unequal element. It is quite useful to check whether two arrays contain the same corresponding elements or not. This responds when a mismatch occurs. If both arrays have corresponding elements same then this function returns -1. We can understand its working by considering the following example:
We are given two arrays, array1 = {2 , 6 , 1 , 10} and array2 = {2 , 6 , 11 , 12} and we want to find the index at which array1 and array2 have first unequal element. As the first two indices have the same set of corresponding elements so the index is 2.
We can achieve the above task without iterating over the arrays with the help of mismatch() method.
3) Arrays.compare(): Added new method to compare the elements of the two provided arrays. Arrays compare() method in Java comes under the Arrays class and java.util package. This method compares two arrays lexicographically (Dictionary order). There are two different versions of different overloads for Boolean, byte, char, double, float, int, long, short, and Object arrays. This method returns values as per the below-mentioned cases.
- It returns 0 if the array is equal to the other array.
- It returns a value less than 0 if the array is lexicographically less than the other array
- It returns a value greater than 0 if the array is lexicographically greater than the other array (more characters).
- A null array is lexicographically less than a non-null array, and the two arrays are considered equal if both are null so that it will print 0 in this case.
Example:
- Added more overloaded methods to the Arrays.equals(). Two arrays are considered equal if both arrays contain the same number of elements, and all corresponding pairs of elements in the two arrays are equal. In other words, two arrays are equal if they contain the same elements in the same order. Also, two array references are considered equal if both are null. Arrays class in java provide the method Arrays.equals() to check whether two arrays are equal or not.
5) Enumeration.asIterator(): Added new method that returns an instance of java.util.Iterator.
6)Additionally, some methods like dropWhile, takeWhile, and ofNullable added .
Collection Enhancements in Java 10
Introduced List.copyOf(), Set.copyOf(), and Map.copyOf() for creating immutable copies of existing collections.
List.copyOf(): The List.copyOf() method in Java 10 allows you to create an unmodifiable copy of a list, and it can be applied to lists of different data types like integers, strings, custom objects, etc. The returned list is immutable, meaning any modification attempts will throw an UnsupportedOperationException. Moreover, the list cannot contain null elements; any list containing null will cause a NullPointerException. In this guide, we will demonstrate how to use List.copyOf() with lists of various types, including integers, strings, and custom objects.
Example:
Set.copyOf() Java 10 Set.copyOf() is introduced to create immutable set from an existing collection. The given collection must not be null and must not have null elements. Any modifications to the given set after the creation of immutable set will not be reflected in the immutable set.
Example:
Map.copyOf() The Map.copyOf() method was introduced in Java 10 as a part of the Java Collections Framework. It is used to create an unmodifiable copy of an existing Map. This method ensures that the resulting Map cannot be modified, meaning no new entries can be added, and no existing entries can be removed or updated. Any attempt to modify the Map will throw an UnsupportedOperationException.
Additionally, the Map.copyOf() method does not accept null keys or values. If the source Map contains null, a NullPointerException will be thrown.
Example:
Collection Enhancements in Java 11
Collection.toArray(IntFunction): Added new default method that allows the collection’s elements to be transferred to a newly created array of a desired runtime type. The new method is an overloaded variant of the existing toArray(….) method. The toArray() method of Collection Interface returns an array containing all the elements present in the collection. The second syntax returns an array containing all of the elements in this collection where the runtime type of the returned array is that of the specified array.
Syntax
public Object[] toArray()
public<T> T[] toArray(T[] a)
Example:
Collection Enhancements in Java 21
Java 21 has introduced three new interfaces in Collection Framework: SequencedCollection, SequencedSet and SequencedMap. These new Collection interfaces offer us to access the first and the last element of it using default methods provided by new library. The feature also allows us to get a reversed view of the collection with a simple method call.
SequencedCollection: Sequenced collections have been introduced in the collections hierarchy to represent collections with elements in a defined encounter order. The new interface abstracts many common operations which were scattered across the Collection type hierarchy in a common place.
Example:
SequencedSet: The SequencedSet interface is specific to Set implementations such as LinkedHashSet. SequencedSet extends SequencedCollection and overrides its reversed() method and the only difference is that the return type of SequencedSet.reversed() is SequencedSet.
Example:
SequencedMap: The The SequencedMap is specific to the Map classes, such as LinkedHashMap. It does not implement the SequencedCollection and provides its own methods that apply the access order to map entries in place of individual elements.
Example: