1. Introduction

Arrays are data structures consisting of a collection of elements identified by an index. The simplest kind is a linear array, also known as a one-dimensional array. Nevertheless, arrays can be multi-dimensional as well.

This tutorial will be a guide to arrays in Scala. We’ll see how to work with Scala arrays and compare them to arrays in Java.

2. Scala Arrays and Java Arrays

Arrays in Scala are mutable, indexed collections of values.

Scala implements arrays on top of Java arrays. For example, an Array[Int] in Scala is represented as a Java int[]. Similarly, an Array[String] in Scala is represented as a Java String[], and so on. Nonetheless, Scala arrays offer much more than Java:

  • they are generic, which means we can work with Array[T], where T is a type parameter;
  • Scala arrays are compatible with Scala sequences, so we can pass an instance of Array[T] where a Seq[T] is required;
  • they support all the operations defined on Scala sequences, so we can invoke the methods defined on Seq[T] on an instance of Array[T].

Generally speaking, an instance of Array[T] can be, at run-time, one of the following Java arrays: byte[], short[], char[], int[], long[], float], double[], boolean[], or an array of objects. The only Scala type generic enough to represent all the previous Java types is AnyRef. Hence, Scala represents instances of Array[T] as instances of Array[AnyRef]. Then, at run-time, Scala performs several type checks on the element being accessed or updated. Such checks determine the actual type of the element, which is needed to execute the right operation on it, however, they also slow down the operation. As a matter of fact, accesses on generic arrays are three to four times slower than accesses over primitive or object arrays.

3. Creating an Array and Accessing Its Elements

The most common way to create an Array in Scala and access its elements is through the apply and update methods:

val array: Array[Int] = Array(1, 2, 3)
println(array(1))
array(1) = 5
println(array(1))

Scala translates the first line in the example above into a call to Array::apply(), defined in the Array companion object. Such a method takes a variable number of values as input and creates an instance of Array[T], where T is the type of the elements. Lines 2 and 4, on the other hand, invoke the apply method defined in the Array class. This time, instead of constructing a new array, apply returns the element at a given index, if defined.

Lastly, the third line invokes Array::update(), which updates the element at index 1. As Scala arrays are mutable, Array::update() does not return a new Array[Int], but instead, modifies the current one.

The example above prints 2 and then 5, showing that the array gets modified by the call to update.

Scala will throw a java.lang.ArrayIndexOutOfBoundsException if we attempt to access or update an element out of the bounds of the array.

4. Scala Arrays and Sequences

Scala arrays support all the operations defined on the Seq type. This is possible thanks to implicit conversions. As a matter of fact, Array is not a subtype of Seq. Instead, the standard library defines a rich wrapper, ArraySeq, which is a subtype of Seq. Additionally, Scala defines an implicit conversion from Array to ArraySeq. Let’s see these conversions in action:

val array = Array(1, 2, 3)
val seq: Seq[Int] = array
val newArray: Array[Int] = seq.toArray

The example above shows that we can freely assign a value of type Array[Int] when one of type Seq[Int] is required. On the other hand, to turn a Seq[Int] into an Array[Int], the conversion must be explicit, using Seq::toArray.

What we saw above turns Arrays into Seqs. Nonetheless, Scala defines another implicit conversion to “add” Seq‘s methods to Array. “Adding”, here, means that we can invoke the methods defined on Seq on an Array, without ending up with a subtype of Seq. This works via an implicit conversion from Array to ArrayOps. Typically, instances of ArrayOps are short-lived objects inaccessible after the call to the desired method. As an optimization, modern JVMs are often able to avoid the creation of this object.

Let’s invoke the reverse method on an instance of Array[Int]:

val array: Array[Int] = Array(1, 2, 3)
val seq: Seq[Int] = array
val arrayReversed: Array[Int] = array.reverse
val seqReversed: Seq[Int] = seq.reverse

Invoking reverse on Array[Int] returns an instance of Array[Int] as a result. This works because Scala applies the intArrayOps implicit conversion, defined in Predef, which returns an instance of ArrayOps[Int]. Lastly, ArrayOps[Int]::reverse returns an instance of Array[Int], preserving the original type. On the other hand, calling reverse on seq will return an instance of Seq[Int], as expected.

5. Multi-Dimensional Arrays

Multi-dimensional arrays are arrays whose elements are arrays. In this section, we’ll see a few ways to create and print them.

5.1. Creating Multi-Dimensional Arrays

The simplest way to create a multi-dimensional array in Scala is with the Array::ofDim method, returning an uninitialized array with the given dimensions. For example, Array.ofDim(3, 2) will create a matrix with three rows and two columns. However, Array::ofDim will not set the elements of that array to any value. Hence, trying to read them without initializing them beforehand will result in a java.lang.ArrayStoreException.

There are other ways to create multi-dimensional arrays and initialize their elements at the same time. Let’s see two of them:

val matrixFill = Array.fill(3, 2)(0)
val matrixTabulate = Array.tabulate(3, 5) { case (_, c) => c }

Array.fill creates a multi-dimensional array where the dimensions are specified in the first parameter list. For example, Array.fill(3, 2) creates a two-dimensional array with three rows and two columns, similarly to Array.ofDim. The difference is the third argument, which is the value that Scala will use to initialize all the elements of the array. The argument is lazily evaluated.

Array.tabulate, on the other hand, lets us specify a function to compute the element. The parameter to that function is a tuple, where the first element is the row index and the second one is the column index. In the example above, the actual value of the elements in the array will be the same as the column index.

5.2. Printing Multi-Dimensional Arrays

Printing multi-dimensional arrays is not an easy task. Generally speaking, the way we print them depends on the number of dimensions. Let’s define a function to print a two-dimensional array:

def matrix2String[T](matrix: Array[Array[T]]): String =
    matrix.map(_.mkString(" ")).mkString("\n")

println(matrix2String(matrixFill))
println("--")
println(matrix2String(matrixTabulate))

matrix2String works differently based on the array dimension. First, with matrix.map, we turn the rows into strings by separating each element of the row with whitespace. Then, with mkString(“\n”), we separate each row with a new line. If we run the println statements, using the matrices we defined earlier, we’ll see the content of the arrays in a tabular form:

0 0
0 0
0 0
--
0 1 2 3 4
0 1 2 3 4
0 1 2 3 4

As expected, the first array only contains 0, whereas, in the second, the elements are the same as the column indexes.

6. Conclusion

In this article, we saw how arrays in Scala work. First, we compared them with their Java counterpart. Secondly, we saw how to create an array and how to read and update its elements. We then explored the interoperability of Array and Seq in Scala and concluded with a brief overview of multi-dimensional arrays.

As usual, you can find the code over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.