1. Overview

Arrays are a crucial concept in programming. Scala provides several types of array classes.

In this tutorial, we’ll discuss the difference between Array, ArraySeq, ArrayOps, and WrappedArray, and learn more about these classes.

2. Array and ArraySeq

Let’s start with the Array class. Array corresponds to the Java array. Therefore, there is no overhead on the bytecode level.

To check this, let’s create a simple example:

val simpleArray: Array[Int] = Array(1, 2, 3, 4, 5)
print(simpleArray)

The print statement will output an address of the array:

[I@30b7c004

Next, if we compile this code using scalac and look at the bytecode in the .class files, we’ll have the following VM instructions:

L0 {
  iconst_5
  newarray 10
  dup
  iconst_0
  iconst_1
  iastore
  dup
  iconst_1
  iconst_2
  iastore
  dup
  iconst_2
  iconst_3
  iastore
  dup
  iconst_3
  iconst_4
  iastore
  dup
  iconst_4
  iconst_5
  iastore
  astore 1
}

As we can see from the instructions above, newarray 10 is called to create an array. Consequently, these instructions correspond to the following Java code:

int[] simpleArray = new int[]{1, 2, 3, 4, 5};

On the bytecode level, it uses primitive types. So a Scala Array[Int] is converted to a Java int[] array under the hood. The same applies to Double, Float, Long, and other “boxed” types.

Scala offers more functionality of the Array class than Java:

  • arrays are generic, so we can create an Array[T] where T can be an abstract or concrete type
  • arrays are Seq-compatible. We can pass an Array[T] where a Seq[T] is expected.
  • arrays support all sequence operations

Consequently, we can also use arrays as sequences even if Scala Arrays are mapped to native Java arrays. Under the hood, this is done by the implicit cast to the ArraySeq class.

Therefore, the following code is absolutely valid:

val simpleArray: Array[Int] = Array(1, 2, 3, 4, 5)
val seq: collection.Seq[Int] = simpleArray
print(seq.reverse)

The result of the reverse method call will be an instance of WrappedArray for Scala 2.12 and ArraySeq for Scala 2.13 and above.

// Scala 2.12
WrappedArray(5, 4, 3, 2, 1)
// Scala 2.13+
ArraySeq(5, 4, 3, 2, 1)
warning: there was one deprecation warning (since 2.13.0); re-run with -deprecation for details

Let’s have a look at the bytecode of the previous example:

getstatic scala/Predef$.MODULE$:scala.Predef$
aload 1
invokevirtual scala/Predef$.wrapIntArray([I)Lscala/collection/mutable/ArraySeq$ofInt;
astore 2

As we can see, internally, Scala calls the wrapIntArray() method to convert an Array to an ArraySeq instance.

Furthermore, it’s important to note that when an implicit conversion to ArraySeq happens, primitive types get boxed.

3. ArrayOps

The next type we’ll cover is ArrayOps. This class also provides all the sequence features to an Array. We can implicitly convert an Array to an ArrayOps instance:

val simpleArray: Array[Int] = Array(1, 2, 3, 4, 5)
val ops: ArrayOps[Int] = simpleArray
print(ops.reverse)

As we’ll see further, the reverse method returns an Array, so the output will also contain an address of the array.

[I@29176cc1

Let’s see the internal bytecode representation of the snippet above: 

L1 {
 getstatic scala/Predef$.MODULE$:scala.Predef$
 aload 1
 invokevirtual scala/Predef$.intArrayOps([I)Ljava/lang/Object;
 astore 2
}
L2 {
 getstatic scala/Predef$.MODULE$:scala.Predef$
 getstatic scala/collection/ArrayOps$.MODULE$:scala.collection.ArrayOps$
 aload 2
 invokevirtual scala/collection/ArrayOps$.reverse$extension(Ljava/lang/Object;)Ljava/lang/Object;
 checkcast int[]
   invokevirtual scala/Predef$.print(Ljava/lang/Object;)V
}

In order to convert an Array to an ArrayOps instance, the Scala compiler uses the intArrayOps() method.

Moreover, we may note that calling any sequence method on an ArrayOps object returns a simple Array. As a result, we cannot chain multiple calls without “rewrapping” after each call.

Commonly an ArrayOps object is discarded after a sequence method call. In order to retain an object, we can use a WrappedArray class.

4. WrappedArray

A WrappedArray class wraps a plain Array and provides sequence methods. It is similar to ArrayOps. However, instead of returning an Array from a sequence method, a WrappedArray returns a WrappedArray instance as a result of a sequence method call. So it provides a similar behavior as any Seq. Calling a sequence method on a Seq returns a Seq.

We are also able to implicitly convert an Array to a WrappedArray as follows:

val simpleArray: Array[Int] = Array(1, 2, 3, 4, 5)
val wrappedArray: WrappedArray[Int] = simpleArray
print(wrappedArray.reverse)

Here’s the result of the execution:

WrappedArray(5, 4, 3, 2, 1)

However, it’s important to note that starting from Scala version 2.13 the WrappedArray class is deprecated as it provides similar functionality to the ArraySeq class. As a result, if we compile our sample with scala version 2.13 and above, we’ll get a warning:

ArraySeq(5, 4, 3, 2, 1)
warning: type WrappedArray in package mutable is deprecated (since 2.13.0): Use ArraySeq instead of WrappedArray; it can represent both, boxed and unboxed arrays

5. Conclusion

In this article, we’ve learned different representations of an Array in Scala.

To sum up, the main difference between an Array and a WrappedArray is that the Array class represents a structure equal to a Java array without any overhead.

On the other hand, *a WrappedArray adds sequence functionality to an Array. So we can use it like a Seq.*

It’s important to note that starting from Scala version 2.13 we need to use the ArraySeq class instead of the WrappedArray class. However, code containing WrappedArrays is still valid. All methods just return an ArraySeq instead of a WrappedArray.

As always, the source code for the examples is available over on GitHub.


« 上一篇: SBT 介绍