1. Overview

Immutable objects and data structures are first-class citizens in Scala. This is because they prevent mistakes in distributed systems and provide thread-safe data. However, we can also use mutable objects if it’s really necessary.

In this tutorial, we’ll discuss immutable and mutable objects in Scala. We’ll look at how to implement them, and some best practices.

2. Assigning Immutable and Mutable Values

In Scala, when a value is assigned to an immutable variable, we cannot reassign it. If we tried, it would result in a compilation error. On the other hand, we can reassign a value to a mutable variable.

Let’s try this by defining an immutable value using val:

val pi = 3.14

If we attempted to change the value of pi, we’d get a compilation error:

pi = 4

So, if we want to have a changeable value, we need to use a variable declared using var:

var myWeight = 60
assert(myWeight == 60)

Now, we can change the value assigned to myWeight:

myWeight = 65
assert(myWeight == 65)

3. Class Mutability

Besides value assignment to variables, we can use val and var to define public properties in a class.

Let’s define a class that contains both immutable and mutable values:

class ImmutabilityCar(color: String, val wheels: Int, var engine: String) {
  def call(): Unit = {
    println(s"This is a car with $color paint, $wheels wheels, and $engine engine")
  }
}

We can see that in the ImmutabilityCar class there are three categories of properties:

  • private color
  • public immutable wheels
  • public mutable engine

Let’s examine the limitations of each property by creating an object to see what we can do with it:

val myCar = new ImmutabilityCar("blue", 4, "diesel")
myCar.call()

Because they’re immutable, we cannot change the values of color and wheels and would get a compilation error with:

myCar.color = "green"
myCar.wheels = 5

We can only change the value of engine, as it’s a mutable property:

myCar.engine = "electric"
assert(myCar.engine == "electric")
myCar.call()

This outputs:

This is a car with blue paint, 4 wheels, and electric engine

4. Collection Mutability

Scala collection libraries provide us with immutable and mutable collections. Let’s try some examples.

4.1. Immutable Collection

The immutable collection types are defined in the package scala.collection.immutables. However, they have aliases in the scala package, so we can use them right away without an extra import.

Let’s assign an immutable collection Seq to a variable:

val pets = Seq("Cat", "Dog")

The immutable collections appear to offer operators that make changes to them. These operators maintain immutability because they return a new collection with the changes applied. They leave the original collection unchanged:

val myPets = pets :+ "Hamster"
val notPets = pets ++ List("Giraffe", "Elephant") 
val yourPets = pets.updated(0, "Mice")

assert(pets == Seq("Cat", "Dog"))
assert(myPets == Seq("Cat", "Dog", "Hamster"))
assert(notPets == Seq("Cat", "Dog", "Giraffe", "Elephant"))
assert(yourPets == Seq("Mice", "Dog"))

Here, pets remains unchanged and we have new collections assigned to myPets, notPets, and yourPets.

4.2. Mutable Collection

To define a mutable collection, we need to import from scala.collection.mutable.

Let’s use a mutable collection ArrayBuffer:

import scala.collection.mutable.ArrayBuffer

val breakfasts = ArrayBuffer("Sandwich", "Salad")

We can add, update, or remove elements from a mutable collection.

Let’s start with adding elements to the collection:

breakfasts += "Bagels"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Bagels"))

breakfasts ++= Seq("PB & J", "Pancake")
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Bagels", "PB & J", "Pancake"))

This changes the contents of breakfasts, adding Bagels, PB & J, and Pancake.

We can also update an element in breakfasts:

breakfasts.update(2, "Steak")
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "PB & J", "Pancake"))

It will change the element in index number 2 to Steak.

We can also remove elements in breakfasts:

breakfasts -= "PB & J"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "Pancake"))

breakfasts -= "Fried rice"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "Pancake"))

We should note that -= can be used to remove an element from a collection if it’s present.

In some mutable collections, like Array, we cannot add or remove an element. We can only update the elements in the collection:

val lunches = Array("Pasta", "Rice", "Hamburger")
lunches.update(0, "Noodles")
assert(lunches sameElements Array("Noodles", "Rice", "Hamburger"))

5. Mutability Best Practices

Functional programming has a concept called referential transparency.

An expression is referentially transparent if it can be replaced with its value without changing the program’s behavior. This can be achieved by using immutable objects. And, this is why immutable objects are the first-class citizens in Scala.

With immutable objects, we can use concurrency safely. This is because there is no danger in having multiple threads access the same data at the same time when it cannot be changed.

However, using immutable objects and collections can cause performance issues. As we have seen, constructing a new copy of a collection in order to add an element is less efficient than modifying an existing collection.

Where we need to, therefore, we can use mutable objects instead.

6. Conclusion

In this article, we’ve discussed mutability in Scala.

To begin with, we explored Scala’s immutable and mutable variables and how that maps onto class properties. Then we examined immutable and mutable collections and how to use them.

Finally, we discussed best practices for mutability in Scala.

As usual, the example code from this article is available over on GitHub.


« 上一篇: Scala Futures 指南
» 下一篇: Scala 中的类和对象