What every developer should know about java.util.Collection

March 30, 2020

by — Posted in Core Java

Hello! This post provides an overview of java.util.Collection interface, that is a root for all Java Collections Framework (except of maps).

What are Java collections?

This section provides a helicopter view on Java Collections Framework. It offers for developers a unified architecture for representing and manipulating collections, enabling collections to be manipulated independently of implementation details. Java defines a collection as an object that represents a group of objects. So, Java Collections Framework (JCF) includes a number of interfaces and implementations that facilitate data operations like searching, sorting, insertion, manipulation, or deletion of elements.

Take a look on the graph below:

Graph 1. Java Collections Framework hierarchy

One of the common interview questions sounds like “why do we use JCF”? Well, there is a number of reasons, why JCF is so useful and important:

  • JCF reduces programming efforts, because you don’t need to reinvent these data structures and algorithms yourself
  • JCF offers high-performance implementations of data structures and algorithms, therefore it increases performance
  • JCF establishes interoperability between unrelated APIs
  • JCF makes it easier to learn collections!

The root classes here are java.util.Collection and java.util.Map. That is very important to remember, as it is very common to think that Map is a collection. While maps contain collection-view operations, which enable them to be manipulated as collections, from a technical point of view, maps are not collections in Java.

Let explore now the java.util.Collection interface deeper as it is base class for a number of other data structures.

java.util.Collection in details

As it was already mentioned, Java collection is a group of objects, that are known as its elements. Note, that elements in some collections have to be unique, while other types permit duplicates. java.util.Collection is not implemented directly, rather Java has implementations of its subinterfaces. This interface is typically used to pass collections around and manipulate them with max degree of generality.

Inserting elements

Advice

Code examples in this post use AssertJ library to write fluent assertions. You can learn more on how to utilize AssertJ with Java Collections in this tutorial

There are two ways to insert new elements to collection:

  • Add a single element
  • Add all elements from the another collection.

Note, that both of these methods are marked optional. That meands, that concrete implementations are permitted to not perform one or more of these operations (in such cases they throw UnsupportedOperationException when insertion is performed).

Take a look on the code snippet below:

@Test
public void insertTest(){
    // get mock posts
    List<Post> posts = getPosts();

    // Add single element
    Post post = new Post(6, "Phasellus scelerisque", "Phasellus scelerisque eros id lacus auctor");
    posts.add(post);
    assertThat(posts).contains(post).hasSize(6);

    // add multiple elements
    List<Post> newPosts = new ArrayList<>();
    newPosts.addAll(posts);
    assertThat(newPosts).containsAll(posts);
}

In examples I use an ArrayList implementation that is one of the Java collections and which implements both add() and addAll() methods. ArrayList has two add() methods, but we concentrate here on the Collection’s one.

To sum up, java.util.Collection permits us to insert elements in these ways:

  • boolean add(Element e) = this method adds a new element and ensures collection contains the specified element. So it returns either true or false depending if this collection changed as a result of the call.
  • boolean addAll(Collection c) = this method inserts all elements from the argument to the collection. Note, that this method also returns boolean value that is true if this collection changed as a result of the call

Some implementations have restrictions on the elements that they may contain, and as the result they prohibit certain insertions. For example, if collection does not allow duplicates, you can’t add an element that already exists. Same goes for null values.

Removing elements

Compare to two inserting methods, there are more methods to delete elements from the collection. Take a look on them:

  • void clear() = removes all of the elements from the collection
  • boolean remove(Element e) = removes a single instance of the specified element e from the collection (if that element is presented)
  • boolean removeAll(Collection c) = deletes all of the collection’s elements that are also contained in the argument’s collection
  • boolean removeIf(Predicate filter) = deletes all of the elements of this collection that satisfy the given predicate.

Let have a look on the example below:

@Test
public void removeTest(){
    // get mock posts
    List<Post> posts = getPosts();

    Post post = posts.get(2);
    assertThat(posts).contains(post);

    // remove object
    posts.remove(post);
    assertThat(posts).doesNotContain(post);

    // clear
    posts.clear();
    assertThat(posts).isEmpty();

    // delete with predicate
    posts = getPosts();
    // remove posts with ID 2 and 4
    posts.removeIf(p -> p.getId() % 2 == 0);
    assertThat(posts).hasSize(3);
}

It is important to remember that not all of these methods are optional. removeIf method is not optional, while remove, removeAll and clear are optional operations.

Create a stream

Advice

In this section I use Java streams. If you’re not familiar with them, feel free to check my guide on Java Streams API

In Java stream stands for a sequence of elements supporting sequential and parallel aggregate operations. java.util.Collection has two methods to create streams (both are not optional):

  • Stream<E> stream() = creates a sequential stream with this collection as its source
  • Stream<E> parallelStream() = creates a possibly parallel stream with this collection as its source.

Let have a look on the code snippet below:

@Test
public void streamTest(){
    List<Post> posts = getPosts();
    Stream<Post> stream = posts.stream();
    assertThat(stream).isInstanceOf(Stream.class);
}

Iteration

From a technical point of view, iterations stands for a technique used to sequence through a block of code repeatedly until a specific condition either exists or no longer exists. Java provides us several approaches to iterate over a collection. Note, that not all collections provide us way to access an element on a base of index (for instance, sets do not). Therefore in this section we will not explore popular iteration approaches, which will not work for all collections. Rather we will concentrate on approaches, that can be used with any collection.

As Java collections are also Iterable let explore how it permits us to go through elements of collection:

  • Using iterators
  • Using streams
  • Using forEach method
@Test
public void iterationTest(){
    List<Post> posts = getPosts();

    // create iterator
    Iterator<Post> iterator = posts.iterator();
    // Option 1 with hasNext
    System.out.println("Iteration using iterator hasNext");
    while(iterator.hasNext()){
        Post post = iterator.next();
        System.out.println(post);
    }

    Iterator<Post> iterator2 = posts.iterator();
    // Option 2 using forEachRemaining
    System.out.println("Iteration using iterator forEachRemaining");
    iterator2.forEachRemaining(p -> System.out.println(p));

    // using forEach
    System.out.println("Iteration using forEach");
    posts.forEach(System.out::println);

    // using stream
    System.out.println("Iteration using stream");
    posts.stream().forEach(p -> System.out.println(p));
}

Basically, iterator pattern permits all elements of the collection to be accessed sequentially, with some operation being performed on each element. You can note that iterator has two core methods:

  • hasNext() – this method returns true if the iteration has more elements and we use it in the while loop (like next() in ResultSet)
  • next() – returns an element and we use it to access the current element of iteration

You can also use forEachRemaining method. It accepts a Consumer function that is executed for each remaining element until all elements have been processed or the action throws an exception.

Note, that iterator() method is not optional.

Another approaches are forEach method and using Streams API.

Access individual element of the collection

I have to say here that java.util.Collection does not contain methods to access individual elements. Each implementation provides its own ways, for instance list elements can be accessed by index, while sets do not support this approach. It is very important to remember that there is no way to access Collection’s elements, as it depends on its subsclasses.

Other Collection methods

We will not move into detailed explanations for these methods, however they are still important to know. I group them under this section:

  • contains(Element e) = returns true if this collection contains the specified element. Uses equals() of object in order to check an equality of the element.
  • containsAll(Collection c) = returns true if this collection contains all of the elements in the specified collection.
  • isEmpty() = returns true if the collection is empty
  • size() = gets an integer value with a number of elements in the collection
  • toArray() = creates an array Element[] from the elements of the collection

Note, that mentioned methods are not optional.

Source code

You can find the full source code for this post in this github repository. If you have questions regarding this post, don’t hesitate to contact me. Have a nice day!