Java Collections: The List Interface

Introduction

The Java Collections Framework is a fundamental and essential framework that any strong Java developer should know like the back of their hand.

A Collection in Java is defined as a group or collection of individual objects that act as a single object.

There are many collection classes in Java and all of them extend the java.util.Collection and java.util.Map interfaces. These classes mostly offer different ways to formulate a collection of objects within a single object.

This is exactly the type of situation where you'd probably rather shoot yourself in the leg - becasuse arrays don't resize.

To add an element, we have to make a copy of the array into a new array, instantiate it with the new elements and assign the new array to our reference variable.

Arrays are a low-level construct and don't give us many features whereas Collections are made to combat that very problem and offer many features and great functionality.

Collections

The Java Collections Framework ships with the JDK itself. It's worth remembering that in the old days, especially for people who wrote C code, developers weren't presented with data structures to choose from. In fact, people used to write their own data structures, which some do even today.

There are legitimate performance reasons why someone might find a custom data structure to be great for a specific project. But, for most developers, relying on the existing framework is a good choice.

Java is used to build big and complex systems and applications. That being said, almost every Java application will end up using the collections framework at one point or another.

All of the collection classes have an underlying data structure that they're implementing - Trees, HashTables, HashMaps, Queues, etc. Implementing these data structures yourself, while potentially fun, can be very hard - there are many corners you have to get right. There's no need to reinvent the wheel if it's already served to you unless you wish to practice and challenge yourself to come up with innovative and alternative solutions.

We'll be taking a look at a few different types of collections in Java:

Lists - A sequential (ordered) Collection. They keep track of the positions of all elements, like arrays and offer search, iteration and range-view operations of their elements. Lists can have duplicate elements.

Sets - Enforces uniqueness constraints - can't contain duplicate elements. It doesn't concern itself with the order of iteration within itself, as it models the mathematical set abstraction. Sets offer no additional functionality other than the inherited from Collections.

Queues - Introduce modification order, that's to say, if you add elements in a certain order, you have to follow a certain order. Queues offer additional insertion, removal and inspection operations upon its elements. It's unique for Queues to follow the FIFO (First in, first out) structure.

Deques - Similar to queues, double-ended queues (shortened to deques) additionally offer the possibility to perform operations on elements from both sides of the queue.

Maps - Although implementations of java.util.Map aren't considered "true collections", they offer collection-view operations which practically enables them collection-level manipulation. This collection isn't a collection of individual values, but pairs. These are associations between unique keys and values (Maps) that can be looked up from those keys. It's important to note that the keys are unique, and each key is associated with a value, but a value can be associated with more than one key.

Interface Collection

As mentioned above, all of the collection interfaces within the Java API extend a common interface - java.util.Collection. This main interface provides all of the common collections functionality.

Each sub-interface has several implementations, and some of these sub-interfaces offer additional operations:

The key point to understand is that each interface defines behavior and functional characteristics where we can utilize multiple data structures while implementations define performance characteristics, utilize a specific data structure and are instantiable.

The most commonly used methods in the Collection interface are:

Method Name

Method Description

size()

Get the number of elements in the Collection

isEmpty()

True if size() == 0, false otherwise

add(element)

Add the element at the beginning of this collection

addAll(collection)

Add all the elements of the argument collection to this collection

remove(element)

Remove the element from this collection

removeAll(collection)

Remove all the elements of the argument collection to this collection

retainAll()

Remove all the elements of this collection not in the argument collection

contains(element)

True if the element is in this collection, false otherwise

containsAll(collection)

True if all the elements of the argument collection are in this collection

clear()

Remove all elements from this collection

Lists

The first, and probably most commonly used interface - java.util.List.

Each element within the list has an index, an int value which defines their position. The indexing count starts at 0, the same as the indexing we can encounter with arrays.

The java.util.List interface also adds a couple of other operations beyond the regular common collection operations:

get(int index)

set(int index, Object object)

These operations are pretty self-explanatory and don't need further explanation. Though, let's take a look at some code examples.

Note: We're instantiating the list as its concrete implementation ArrayList. In most cases, we'd use this implementation for a List.

Another note: You can specify the initial size of the ArrayList via the constructor to avoid resizing if you know a definitive size.

The interface also provides another version of the add() method, including an index. In this case, we add the element to the given index, and if the index is already taken by another element, all of the elements after the added one shift to the right by one:

products.add(2, "Pen");
System.out.println(products);

Output:

[Mug, Wallet, Pen, Phone]

Retrieving Elements

Using the get() method with the given index, we can retrieve a specific element in the list:

System.out.println(products.get(0));

Output:

[Mug]

Removing Elements

Using the remove() method, we can remove an element from the list. Calling this method will return the element as well as shift the elements after it one index back, to fill in the now existent hole in the sequence:

System.out.println(products.remove(1));

Output:

[Wallet]

Setting Elements

Using the set() method, we can replace an existing element given an index:

products.set(1, "Book");
System.out.println(products);

Output:

[Mug, Book, Phone]

Searching for Elements

Using the indexOf() method, we can also look up values, given an index. If the search fails, and no object exists with the given index, the list will return -1. In the case of multiple equal objects, the list will return only the first index.

Using the lastIndexOf() will return the last index of the given element.

System.out.println(products.indexOf(5));

Output:

-1

Iterating Elements

Although possible to iterate with for and enhanced-for loops, the interface provides two new helper classes that allow us to iterate through lists - Iterator and ListIterator:

Generally speaking, ArrayList is much more commonly used than LinkedList. And to quote Joshua Bloch, the man who wrote LinkedList:

"Does anyone actually use LinkedList? I wrote it, and I never use it."

Performance Comparison

Due to their different natures, these implementations have different approaches and method runtimes.

Depending on the requirements, you'll have to choose which to use. Generally speaking, due to its doubly-linked nature, LinkedList is good for frequent addition and removal whereas ArrayList is good for searching due to random access.

Conclusion

The Java Collections framework is a fundamental framework that every Java developer should know how to use.

In the article, we've talked about collections in general, the problems with arrays and how the framework combats them. Afterwards, we jumped into the implementations of this interface, their advantages, and disadvantages, as well as the operations you'll most certainly use at one point or another.