Java Collections Framework

 

A Java collection is a Java object that acts as a container for a group other objects. New elements can be added to a collection and existing elements can be removed from a collection. Collections provide multiple ways to access to their elements, utilizing performance and code cleanness, for different sets of requirements. For example, a list of Strings holds String objects with order and provides access to its elements using indexes. Another example might be a map that provides residence addresses for people. Such a map would store people’s social security numbers as keys and addresses as values. Such a map would provide fast access to getting people’s addresses using social security numbers when needed.

Java language comes with a built-in framework of collections, consisting of interfaces and classes, called Java Collections Framework. Java Collections Framework provides the interfaces that declare the collection methods and comes with the implementations for these interfaces. The algorithms used in the implementations provided by the framework can be reused with different collection objects and therefore are referred to as polymorphic.

The collections framework consists mainly of two interfaces: java.util.Map and java.util.Collection. Everything descending from the java.util.Collection interface is a collection. Map interface does not extend Collection interface and therefore it is a different branch. Still, Map interface and its descendants are considered part of the Java Collections Framework. All the interfaces and classes in the Java Collections Framework are generic.

What does ordered collection mean?

For a collection, ordered means the elements of the collection are kept in a specific order that is preserved as new elements are added or removed. In an ordered collection, every element has an index which represents the element’s order in the collection. Index of elements in the collection starts from 0, just like in arrays. The first element of the collection has the index 0, the second one’s index is 1 and it goes on this way until the last element, which has the index <number of elements in the collection> – 1. If we create an empty ordered collection and add an element to it, this element gets index 0. Then the second one gets index 1. When an element is removed from an ordered collection, all the elements with an index greater than that of the removed element are shifted up the collection by 1 index. That is, if the element with index 2 is removed, the element at index 3 gets its place and has the index 2; the element at index 4 becomes the one at index 3 and this algorithm is applied to all the elements with an index greater than the element that is removed.

What does sorted collection mean?

For a collection, sorted means the elements of the collection are kept ordered using a sorting rule, like alphabetical order for a collection of Strings or numeric order for a collection of Integer.. The sorting rule is called the sort order. For example, in a sorted collection of Strings, when a new element is added, its index is not <size of the collection – 1> but it is determined by where it fits alphabetically among the other elements of the collection.

What does synchronized collection mean?

For a collection, being synchronized means the methods provided by the collection to access its elements are synchronized and can be used by one thread at a time. For example, in a synchronized collection, the methods for adding an element and removing an element are synchronized.

java.util.Collection Interface

Java Collections Framework defines the java.util.Collection interface which is the blueprint for all collections. All Java collection objects are descendants of java.util.Collection interface. Collection interface provides the following methods to access the objects it contains and operate on them.

void add(Object object): Adds an element to a collection.

void addAll(Collection collection): Adds all the elements in the argument collection to the collection that invokes the method.

void remove(Object object): Removes an element from a collection.

void removeAll(Collection collection): Removes all the elements of the argument collection from the collection that invoked the method.

boolean contains(Object object): Returns true if a collection contains the object, false otherwise.

boolean containsAll(Collection collection): Returns true if all the elements of the argument collection are contained by the collection that invoked the method, false otherwise.

void clear(): Removes all the elements of the collection and leaves the collection with no elements.

int size(): Returns the number of elements contained by the collection.

Iterator<E> iterator: Returns all the elements of the collection as an iterator. It is possible to loop over this iterator and access every single element of the collection.

Java Collections Framework provides 4 interfaces that extend the java.util.Collection interface: List, Set, Queue, Dequeue.

java.util.List

A list is an object that keeps its elements ordered and assigns an index to each of its elements, starting from 0. A list uses a backing array that grows in size to hold its elements. The elements of a List are ordered by the order they are added. The java.util.List interface is the blueprint for all the lists defined by the Java Collections Framework.

The elements of a list can be sorted with their natural order using java.util.Collections.sort(List<T> list) method.

The following methods are used to access the elements of a list using indexes:

  • Object get(int index): Returns the element at the index.
  • Object set(int index, Object o): Sets the object at index index if an object was already held at that index. If that index was not holding an object before, an ArrayOutOfBoundsException is thrown.
  • The following list implementations are provided by Java Collections Framework.
Concrete Class Ordered Sorted Synchronized Description
ArrayList Yes, by insertion order No No Fast iteration, not good for use with lots of inserts and deletes.
LinkedList Yes, by insertion order No No Elements are doubly-linked to each other, providing faster insertion and deletion but not good for iteration.
Vector Yes, by insertion order No Yes Like ArrayList but with poor performance because its methods are synchronized.

All list implementations are ordered but not sorted. Among the list implementations, only Vector is synchronized.

Let’s see lists with an example.

public class MyClass {

public static void main(String [] args) {

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

collection.add(“string1”);

collection.add(“string2”);

collection.add(“string3”);

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

System.out.println(“index “ + i + “ : “ + s);

}

System.out.println(“Modified collection:”);

collection.set(0, “newString1”);

collection.remove(1);

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

System.out.println(“index “ + i + “ : “ + s);

}

}

}

The output would be

index 0 : string1

index 1 : string2

index 2 : string3

Modified collection:

index 0 : newString1

index 1 : string3

Here in this example, a String List reference is declared and initialized to be an instance of ArrayList. Following that, three strings are added to the list, making the size of the list 3. Then we loop over the elements of the list and print them on the console. This is followed by setting the first element of the list to a new value and removing the second element in the collection. Then we loop over the elements again with a for loop and print them on the console. As can be seen from the output, when the second element is removed, the elements following it are shifted one index up, filling the open index and the size is reduced by 1.

Alternatively, looping over the elements of the list can be done the same way using a for-each loop as follows, which is the mostly preferred by developers since the release of Java 5.

int i = 0;

for (String s : collection) {

System.out.println(“index “ + i + “ : “ + s);

i++;

}

Using iterators to loop over the elements of a collection

Iterator<E> iterator() method of Collection interface provides an alternative way to access the elements of a collection. Every List implementation comes with its own implemented version of iterator method. Invoking iterator() on a Collection returns a java.util.Iterator object which can be looped over.

Let’s see how iterators work with an example.

public class MyClass {

public static void main(String [] args) {

Collection<String> collection = new ArrayList<String>();

collection.add(“string1”);

collection.add(“string2”);

collection.add(“string3”);

Iterator<String> iterator = collection.iterator();

int i = 0;

while (iterator.hasNext()) {

System.out.println(“index “ + i + “ : “ + iterator.next());

i++;

}

}

}

The output would be

index 0 : string1

index 1 : string2

index 2 : string3

Initially, the pointer of an iterator object points to the beginning of the Iterator. hasNext() method returns true if the iterator has more elements to loop over. Once all the elements of the iterator are exhausted, hasNext() method returns false. next() method gets the element next to the pointer and moves the pointer one element forward. If all the elements have already been consumed, calling next() throws a RuntimeException. One important point about calling next() is that, the same element cannot be obtained by invoking this method. Let’s consider the following example.

while(iterator.hasNext()) {

System.out.println(“element is “ + iterator.next() + “ and the element’s length is “ + iterator.next().length())

}

Here, at first, we might think that we get the element from the iterator and print out on the console the element itself and the length of the element. However, when we call iterator.next() the first time to print out the element on the console, the pointer of the iterator is moved forward by 1 element and it no longer points to the same element. The second call to iterator.next() gives us the next element in the iterator and the length that is printed on the console is the next element’s length. The right approach to this issue would be to call iterator.next() once, assign the element to a local variable and perform the operations on the local variable. The code can be fixed as follows.

while (iterator.hasNext()) {

String s = iterator.next();

System.out.println(“element is “ + s + “ and the element’s length is “ + s.length);

}

java.util.Set

A Set keeps only unique instances of objects. Unlike lists, an element can be added to a set only once and no duplicates are allowed. Adding the same object to a set twice does not cause an exception. It just would not have any effect as the set, in the end, would have only one unique object added. In Java Collections Framework, sets are represented by the java.util.Set interface. Java Collections Framework provides three implementations.

Concrete Class Ordered Sorted Synchronized Description
HashSet No No No Uses the hashcode of the object while inserting into the set
LinkedHashSet Yes, by insertion order No No Same as HashSet but allows iterating over the elements of the set in an ordered fashion
TreeSet No Yes, by natural order No Same as HashSet but keeps the elements sorted

public class MyClass {

public static void main(String [] args) {

Set<String> set = new HashSet<String>();

set.add(“Hello”);

set.add(“World”);

set.add(“We”);

set.add(“Are”);

set.add(“Friends”);

set.add(“Hello”);

set.add(“World”);

Iterator<String> iterator = set.iterator();

int i = 0;

while (iterator.hasNext()) {

System.out.println(“element “ + i + “ : “ + iterator.next());

i++;

}

}

}

The output would be

element 0 : Are

element 1 : We

element 2 : World

element 3 : Friends

element 4 : Hello

The elements are not ordered since the set used in this example is a HashSet. The strings “Hello” and “World” were added to the set twice but the list keeps only one unique String with value “Hello” and one unique String with value “World”. The output would stay the same no matter how many times the program was run because the Collections Framework uses the hashcodes of the elements when new elements are added to a HashSet.

java.util.Queue

A Queue object is like a list in that it keeps its elements as an ordered list. Different than a list, a queue uses first-in-first-out principle while keeping its elements. An element is added to a queue from its front but removed from its rear. This way, the first element added becomes the first element removed. In the Java Collections Framework, queues are represented by java.util.queue interface. The Collections Framework provides one implementation, PriorityQueue which is ordered and sorted but not synchronized.

java.util.Map

Maps are used to keep unique key-value pairs. Every key in a map is unique, in other words, there can be only one occurrence of a key-value pair in a map and the keys of a map constitute a Set. Putting the same key-blue pair in the map more than once does not have an effect and it does not cause an exception either. However, putting the key with a new value in the map replaces the old value of the key.

java.util.Map interface defines the following methods to access its elements.

void put(K key, V value): Adds a new key-value wait to the map

V get(K key): Gets the value part of a key-value pair from the map using the key part of the pair

Set<K> keySet(): Returns the keys of all the key-value pairs kept in the map as a Set.

In the Collections Framework, maps are represented by the java.util.Map interface. This interface does not extend Collection interface and therefore, maps are not really collections. However, java.util.Map interface and its provided implementations are part of the Collections Framework.

Java Collections Framework provides the following Map implementations.

Concrete Class Ordered Sorted Synchronized Description
HashMap No No No Keeps unique key-value pairs and provides basic map functionality for access to its elements
Hashtable No No Yes Like a HashMap, but its methods are synchronized
TreeMap Yes, sorted Yes, using natural order or by implementing comparison logic No Keeps its elements ordered and sorted
LinkedHashMap Yes, by insertion order No No Keeps its elements in a doubly linked list

Let’s see how maps work with an example.

public class MyClass {

public static void main(String [] args) {

Map<String, Integer> map = new HashMap<String, Object>();

map.put(“x”, 1);

map.put(“y”, 2);

map.put(“z”, 3);

Set<String> keys = map.keySet();

for (String key : keys) {

System.out.println(“key: “ + key + “, value: “ + map.get(key));

}

}

}

The output would be

key:x, value: 1

key:y, value: 2

key:z, value: 3

Here, in this example, we initialize a HashMap instance and put three values in the map.