Collections

Mutable and Immutable Collections

Language

Scala collections systematically distinguish between mutable and immutable collections. A mutable collection can be updated, reduced or extended in place. This means you can change, add, or remove elements of a collection as a side effect. Immutable collections, by contrast, never change. You still have operations that simulate additions, removals, or updates, but those operations will in each case return a new collection and leave the old collection unchanged.

All collection classes are found in the package scala.collection or one of its sub-packages mutable and immutable. Most collection classes needed by client code exist in three variants, which are located in packages scala.collection, scala.collection.immutable, and scala.collection.mutable, respectively. Each variant has different characteristics with respect to mutability.

A collection in package scala.collection.immutable is guaranteed to be immutable for everyone. Such a collection will never change after it is created. Therefore, you can rely on the fact that accessing the same collection value repeatedly at different points in time will always yield a collection with the same elements.

A collection in package scala.collection.mutable is known to have some operations that change the collection in place. So dealing with a mutable collection means you need to understand which code changes which collection when.

A collection in package scala.collection can be either mutable or immutable. For instance, collection.IndexedSeq[T] is a superclass of both collection.immutable.IndexedSeq[T] and collection.mutable.IndexedSeq[T]. Generally, the root collections in package scala.collection support transformation operations affecting the whole collection, the immutable collections in package scala.collection.immutable typically add operations for adding or removing single values, and the mutable collections in package scala.collection.mutable typically add some side-effecting modification operations to the root interface.

Another difference between root collections and immutable collections is that clients of an immutable collection have a guarantee that nobody can mutate the collection, whereas clients of a root collection only promise not to change the collection themselves. Even though the static type of such a collection provides no operations for modifying the collection, it might still be possible that the run-time type is a mutable collection which can be changed by other clients.

By default, Scala always picks immutable collections. For instance, if you just write Set without any prefix or without having imported Set from somewhere, you get an immutable set, and if you write Iterable you get an immutable iterable collection, because these are the default bindings imported from the scala package. To get the mutable default versions, you need to write explicitly collection.mutable.Set, or collection.mutable.Iterable.

A useful convention if you want to use both mutable and immutable versions of collections is to import just the package collection.mutable.

import scala.collection.mutable

Then a word like Set without a prefix still refers to an immutable collection, whereas mutable.Set refers to the mutable counterpart.

The last package in the collection hierarchy is scala.collection.generic. This package contains building blocks for abstracting over concrete collections.

For convenience and backwards compatibility some important types have aliases in the scala package, so you can use them by their simple names without needing an import. An example is the List type, which can be accessed alternatively as

scala.collection.immutable.List   // that's where it is defined
scala.List                        // via the alias in the scala package
List                              // because scala._
                                  // is always automatically imported

Other types aliased are Iterable, Seq, IndexedSeq, Iterator, LazyList, Vector, StringBuilder, and Range.

The following figure shows all collections in package scala.collection. These are all high-level abstract classes or traits, which generally have mutable as well as immutable implementations.

General collection hierarchy

The following figure shows all collections in package scala.collection.immutable.

Immutable collection hierarchy

And the following figure shows all collections in package scala.collection.mutable.

Mutable collection hierarchy

Legend:

Graph legend

An Overview of the Collections API

The most important collection classes are shown in the figures above. There is quite a bit of commonality shared by all these classes. For instance, every kind of collection can be created by the same uniform syntax, writing the collection class name followed by its elements:

Iterable("x", "y", "z")
Map("x" -> 24, "y" -> 25, "z" -> 26)
Set(Color.red, Color.green, Color.blue)
SortedSet("hello", "world")
Buffer(x, y, z)
IndexedSeq(1.0, 2.0)
LinearSeq(a, b, c)

The same principle also applies for specific collection implementations, such as:

List(1, 2, 3)
HashMap("x" -> 24, "y" -> 25, "z" -> 26)

All these collections get displayed with toString in the same way they are written above.

All collections support the API provided by Iterable, but specialize types wherever this makes sense. For instance the map method in class Iterable returns another Iterable as its result. But this result type is overridden in subclasses. For instance, calling map on a List yields again a List, calling it on a Set yields again a Set and so on.

scala> List(1, 2, 3) map (_ + 1)
res0: List[Int] = List(2, 3, 4)
scala> Set(1, 2, 3) map (_ * 2)
res0: Set[Int] = Set(2, 4, 6)

This behavior which is implemented everywhere in the collections libraries is called the uniform return type principle.

Most of the classes in the collections hierarchy exist in three variants: root, mutable, and immutable. The only exception is the Buffer trait which only exists as a mutable collection.

In the following, we will review these classes one by one.

Contributors to this page: