The Scala language, being a mix of object oriented and functional programming paradigms, has a quite unique collection framework, compared to some other languages. Although inheriting a lot from Java and JVM world, the Scala has a much easier and to use API and is overall more polished.
In this post, I will show you a range of Scala collections, how to use them with code samples, and where to use them. Enjoy!
First, let's have a look at the packages that contain all the Scala collections. The top-level package is called as you might expect -
collection. What is more interesting, are the sub-packages included in it. Those contain a more specialized version of typical collections, tailored for the specific use case.
Whether you require parallel processing, concurrent access or just want to have a normal key-based lookup - just import the right package, and you are good to go!
The table below presents the name of
collections sub-package, as well as brief description of what you can find there.
||Defines the base traits and objects needed to use and extend Scala’s collections
library, including all definitions in sub-packages. Most of the abstractions you’ll
work with are defined here.
||Defines a Map trait and TrieMap class with atomic, lock-free access
||Defines types for wrapping Scala collections with Java collection abstractions
and wrapping Java collections with Scala collection abstractions
||Defines reusable components used to build the specific mutable, immutable,
||Defines the immutable collections, the ones you’ll use most frequently.|
||Defines mutable collections. Most of the specific collection types are available
in mutable and immutable forms, but not all.
||Defines reusable components used to build specific mutable and immutable
collections that distribute processing to parallel threads
||Defines parallel, immutable collections.|
||Defines parallel, mutable collections.|
||A deprecated package of tools for observing collection operations.|
As you can see the range of options is very wide. On one hand, it is always good to have an appropriate collection for any use case. On the other hand, it is quite difficult to get started, if you are just looking to implement simple
For the purpose of this post, let's focus on the most widely used collections -
The package of immutable collections is the most popular out of all Scala collections. In fact, this package is included in the scope by default, so we can start using it without any imports.
This package includes lots of both traits and concrete implementations of certain data structures. The image below shows those (blue is a trait, and black is class):
Now, let's set that aside and actually learn to use some of the basic collections. The following sections focus on some of the widely used implementations, one at a time.
The first one on the list is
Set. This is a collection, which contains no duplicate elements. If having only unique elements in the collection, that is the one you should choose. A
Set is created like that:
// Set of Char type val set: Set[Char] = Set('a', 'b', 'c') // Or just empty Set val setOfInt: Set[Int] = Set()
What's interesting about operations in
Set, is the
apply method. The
apply method behaves like
contains method - both check whether the
Set contains a given element:
val set: Set[Int] = Set(1, 2, 5, 6, 9) set.contains(2) // true set(2) // same as above, returns true
Set offers a wide range of operations - full reference can be found here.
Where to use
- Don't want duplicates
- Need to quickly check for the presence of an element
- No need to traverse
Map is a collection of key-value pairs. Every key has a corresponding value assigned to it. Keys have to be unique, in order to provide
O(1) lookup time. Values, however, do not have to be unique. Let's see how we can create a
// A Map of Int to String mapping val map: Map[Int, String] = Map(1 -> "one", 2 -> "two", 3 -> "three") // Or an empty Map val emptyMap: Map[String, String] = Map()
The elements in the
Map, are created using
key -> value syntax, although you can also use
(key, value) if you prefer (first version is preferred though).
What's interesting about the
Map, is how we can get a value, using a key. The default method to get a value is
get(key) method. This method, however, returns an
Option[T], and not
T. That is to accommodate for a situation when a key doesn't exist - instead of an exception, an
Option[None] is returned. If you prefer getting straight value, the apply method will give you that (or exception if none found of course). Let's see how it looks:
val map: Map[Char, Int] = Map('I' -> 1, 'V' -> 5, 'X' -> 10) map.get('I') // returns Option[Int] map.get('D') // returns Option[None] map('V') // returns 5 map('C') // throws exception
Map offers a wide range of operations - full reference can be found here.
You can learn more about
Option in my blog post on it.
Where to use
- Need key-value storage
- Need O(1) lookup and retrieval
- Order is not important
- No duplicate keys required
Vector is an indexed, ordered, and traversable sequence. Despite sounding quite complicated, it's quite popular, general-purpose data structure, thanks to its good balance between fast random selection and fast random functional updates. Let's see how to use them in practice:
// A Vector of Integers val vector: Vector[Int] = Vector(1, 2, 3, 5, 7, 11) // Or an empty Vector val emptyVector: Vector[String] = Vector()
apply method of
Vector, simply gets and element, using and index in the vector. In this data structure, an index must be between zero and the length of a vector - otherwise, and
IndexOutOfBoundsException is thrown:
val vector: Vector[String] = Vector("one", "two", "three") vector(0) // returns "one" vector(3) // exception
Vector offers a wide range of operations - full reference can be found here.
Where to use
- Need fast random access and updates
- Need fast append/prepend/tail operations
- Must be traversable
- Ordering matters
The last collection is the easiest and therefore popular among simple use cases -
List. This collection is implemented as a linked list, which represents ordered collections. Let's see how to use a
// List of Strings val list: List[String] = List("blue", "red", "yellow") // Empty List val emptyList: List[Int] = List()
As mentioned above, a
List is implemented as a linked list. This means, that we can create a list, using prepend operator
:: and a Nil, which represents empty list. This can be done like this:
// Same as example from above val list: List[String] = "blue" :: ("red" :: ("yellow" :: Nil)) // And an empty List val emptyList = Nil
List offers a wide range of operations - full reference can be found here.
Where to use
- Have to be traversable
- Need O(1) prepending and reading of head element
- Can tolerate O(n) appending and reading of internal elements
- Don't need random access
I hope you have found this post useful. If so, don’t hesitate to like or share this post. Additionally, you can follow me on my social media if you fancy so 🙂