Changing an element by index
The entries of an ArrayBuffer can be replaced. You write the index in parentheses on the left side of an assignment, and the value on the right:
val numbers = scala.collection.mutable.ArrayBuffer(50, 40, 30)
numbers(0) = 15
numbers(2) = -1
// numbers is now ArrayBuffer(15, 40, -1)
The element at the given index is replaced, and the buffer keeps its size and the order of the other elements. Only the slots you assign to change.
val and mutation
The buffer above is declared with val, and the assignments are allowed. A val means the name numbers always refers to the same buffer. It says nothing about the buffer's contents. Writing numbers = ArrayBuffer(1) is the thing that fails, with Reassignment to val.
What happens behind the syntax
The compiler turns numbers(0) = 15 into the call numbers.update(0, 15). Reading numbers(0) calls apply instead. Types that define update allow index assignment, and types that don't, don't. A List is immutable and has no update, so list(0) = 5 fails to compile. For a list you ask for a modified copy: list.updated(0, 5) returns a new list and leaves the original alone.
The exercise
The lesson starts with two buffers:
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
- Change the first fruit to
"apple". The first item is at index 0, so writefruits(0) = "apple". - Add 5 to the second age. The second item is at index 1. Read it, add 5 and store the result back:
ages(1) = ages(1) + 5. The right side is computed first, from the old value 32, so the new value is 37.
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
fruits(0) = "apple"
ages(1) = ages(1) + 5
Afterwards, fruits is ArrayBuffer(apple, kiwi, grapefruit) and ages is ArrayBuffer(14, 37, 50, 21). The shorter form ages(1) += 5 does the same thing.
Updating many elements
To change every element, loop over the indexes. The method indices gives the valid ones, from 0 to the size minus one:
for (i <- ages.indices) {
ages(i) += 1
}
With the ages as they are after the exercise, 14, 37, 50, 21, the buffer becomes 15, 38, 51, 22. Looping over the values with for (age <- ages) reads them but can't store anything back, because age is a copy of each number, so use the index when you want to write.
Two names can also refer to one buffer. After val ref = ages, the assignment ref(0) = 9 changes ages(0) as well, because no copy is made.
Gotchas
- Replacing is not inserting. Assigning to an index overwrites the element there. To add a new element at the end, use
+=on the buffer itself, as inages += 99, which makes the size 5. - The index must already exist. Assigning to an index at or past the size throws an
IndexOutOfBoundsException. The buffer does not grow to fit. - Element type. The buffer's type is inferred from its initial values, so
fruitsonly accepts strings andagesonly integers. - Parentheses. The index goes in parentheses, never brackets.
Key takeaways
buffer(i) = valuereplaces the element at indexi.- To update from the old value, read it on the right side:
ages(1) = ages(1) + 5. valkeeps the variable pointing at one buffer, and the buffer's elements can still change.
