Java Collections
Java supports arrays to store multiple values or objects of the same type together. An array is initialized with a predefined size during creation. The size cannot be changed in the future, and that imposes some limitations on their use for solving business problems. If we want to store more data, we need to create a new larger array and then copy the data in this array manually. This can be inefficient for programs that process a lot of data.
Different collections
Fortunately, there is a set of containers called collections for grouping elements into a single unit. They are used to store, retrieve, manipulate, and communicate aggregated data.
Collections are more sophisticated and flexible compared to arrays. First of all, they are resizable: you can add any number of elements to a collection. Also, a collection will automatically handle the deletion of an element from any position. The second point is collections provide a rich set of methods that are already implemented for you.
There are several types of collections with different internal storage structures. You can choose a collection type that best matches your problem so that your most frequent operations will be convenient and efficient.
Actually, collections are representations of different data structures and abstract data types from computer science. It is good to understand the relationship between them and collections in Java. This will help you in programming interviews and in working to select an appropriate collection.
Features of collections
There are several specific features of collections in Java:
- They are represented by different classes from the Java Standard Library.
- All modern collections are generic types while old collections are non-generic. We will only focus on new collections. As regular generics, they can store any reference types including custom classes defined by you (like
Person
or something else). - Collections can be mutable (possible to add and remove elements) and immutable (impossible to modify).
In addition to standard collections, there are a number of external libraries with collections. One such library is Guava Collections which was developed by Google. It can be used if standard collections are not enough to solve your problems.
The simplest collection example
Here is an example of a simple collection called ArrayList
. To use it, make the following import:
java.util.ArrayList;
It works in a similar way to a regular array, but you do not have to manually resize it to add and remove elements.
ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("second");
list.add("third");
System.out.println(list); // [first, second, third]
System.out.println(list.get(0)); // first
System.out.println(list.get(1)); // second
System.out.println(list.get(2)); // third
list.remove("first");
System.out.println(list); // [second, third]
System.out.println(list.size()); // 2
Note, in this example, we used the get
method to access an element by its index. Unlike arrays, collections do not have the []
operator.
We hope this is enough for the first acquaintance with collections. In further topics, you will learn about different kinds of collections in more detail. Now the main thing to understand is that using collections is not more difficult than using a regular array.
All modern collections are generic, so you can specify any reference type as a generic parameter and store it in a collection. But there is one restriction, collections cannot store primitive values at all (int
, long
, char
, double
and so on). You have to use one of the wrapper classes (Integer
, Long
, Character
, Double
or another one) instead.
The Collections framework overview
Java programming language provides the collection framework which consists of classes and interfaces for commonly reused data structures such as lists, dynamic arrays, sets, and so on. The framework has a unified architecture for representing and manipulating collections, enabling collections to be used independently of implementation details via their interfaces.
The Java Collection framework includes:
- interfaces that represent different types of collections;
- primary implementations of the interfaces;
- legacy implementations from earlier releases (known as "old collections");
- special-purpose implementations (like immutable collections);
- algorithms represented by static methods that perform useful operations on collections.
In this topic, we will only consider the basic interfaces from the collections framework placed in the java.util
package.
Commonly used interfaces
There are two root generic interfaces Collection<E>
and Map<K,V>
, and some more specific interfaces to represent different types of collections.
The Collection<E>
interface represents an abstract collection, which is a container for objects of the same type. It provides some common methods for all other types of collections.
The interfaces List<E>
, Set<E>
, Queue<E>
, SortedSet<E>
, and Deque<E>
represent different types of collections. You cannot directly create an object of them since they are just interfaces. But each of them has several implementations. As an example, the ArrayList
class, that represents a resizable array, is a primary representation of the List<E>
interface. Other interfaces, as well as their implementations, will be considered in the following topics.
Another root interface is Map<K,V>
that represents a map (or dictionary) for storing key-value pairs, where K
is the type of keys and V
is the type of stored values. In the real world, a good example of a map is a phone book where keys are names of your friends and values are their phones. The Map<K,V>
interface is not a subtype of the Collection
interface, but maps are often considered as collections since they are part of the collections framework and have similar methods.
Note, the Collection
and Map
interfaces do not extend each other.
The Collection interface
Here are some common methods provided by the Java Collection
interface.
int size()
returns the number of elements in this collection;boolean isEmpty()
returnstrue
if this collection contains no elements;boolean contains(Object o)
returnstrue
if this collection contains the specified element;boolean add(E e)
adds an element to the collection. Returnstrue
, if the element was added, else returnsfalse
;boolean remove(Object o)
removes a single instance of the specified element;boolean removeAll(Collection<?> collection)
removes elements from this collection that are also contained in the specified collection;void clear()
removes all elements from this collection.
It is possible to refer to any particular collection via this base interface since, as you know, the superclass can be used to refer to any subclass object derived from that superclass.
Let's create a collection named languages
and add three elements to it:
Collection<String> languages = new ArrayList<>();
languages.add("English");
languages.add("Deutsch");
languages.add("Français");
System.out.println(languages.size()); // 3
This approach allows you to replace the concrete collection at any time without changing the code that uses it. It also fosters software reuse by providing a standard interface for collections and algorithms to manipulate them. It may sound complicated now, but the more you work with collections, the more understandable it will become.
It is impossible to get an element by index via the Collection
interface because it is very abstract and does not provide such a method. But if it doesn't matter to you which particular collection to use, you can work via this interface.
It is important to understand that the order of the elements in the ArrayList
is still preserved. We simply cannot call the get
method via the Collection
interface.
Every collection can be cast to a string by using toString
and compared with another collection using the equals
method. These methods come from Object
and their behavior depends on elements stored in the collection and the type of the collection itself.
Mutable and Immutable collections
All collections can be divided into two groups based on their mutability: mutable and immutable. They both implement the Collection<E>
interface, but immutable collections will throw an UnsupportedOperationException
when you try to invoke some methods which change them: for example, add
, remove
, clear
.
In the next topics, we will consider how to create and when to use immutable collections. For now, just remember that they exist.
Iterating over collections
If you would like to iterate over all elements of any collection, you can use the for-each style loop. Let's return to our languages
collection:
for (String lang : languages) {
System.out.println(lang);
}
This code prints all elements of this collection.
English
Deutsch
Français
The order of elements when iterating depends on the particular type of collection that is actually being used.
If you are already familiar with method references or lambda expressions, you can use another style for iterations using the forEach(Consumer<T> consumer)
method:
languages.forEach(System.out::println); // with method reference
languages.forEach(elem -> System.out.println(elem)); // with lambda expression
This looks very readable but is optional for use.
Removing elements
It is also possible to remove elements from a mutable collection (like ArrayList
).
languages.remove("Deutsch");
System.out.println(languages.size()); // 2
Note, the remove
as well as the contains
methods rely on the equals
method of the elements. If you store non-standard classes in the collection, equals
and hashCode
should be overridden.
Again, if you are already familiar with lambda expressions, you can invoke the removeIf
method to remove all of the elements that satisfy the given predicate:
languages.removeIf(lang -> lang.startsWith("E")); // it removes English
System.out.println(languages.size()); // 1
Use any way you like.
Conclusion
Sometimes arrays are not flexible enough to store and manipulate your data. For that, Java provides collections; mostly generic classes from the Java Standard Library or external libraries, that are better suited for storing and managing objects in a more dynamic way. Collections come in different types, both mutable and immutable, and are designed to serve specific purposes depending on the data structure required.
The Java Collections Framework provides a set of interfaces that define common methods for different types of collections. For example, the Collection<E>
interface acts as an abstract container for storing values of the same type. Collections (excluding maps) can be easily iterated over using the for-each
loop or the forEach
method, allowing for more flexible and consistent data handling compared to traditional arrays.