Valid and invalid indexes
An array with length elements has valid indexes from 0 to length - 1. Any other index is invalid. Using one doesn't produce a wrong value or a silent default. The program throws an exception:
val ourArray = Array(50, 40, 30)
// Valid indices: 0, 1, 2
println(ourArray(0)) // 50
println(ourArray(2)) // 30
// Invalid indices: 3, 30, -1
println(ourArray(3)) // throws ArrayIndexOutOfBoundsException
println(ourArray(30)) // throws ArrayIndexOutOfBoundsException
println(ourArray(-1)) // throws ArrayIndexOutOfBoundsException
The exception is thrown while the program runs, and the compiler does not warn about it. Its message names the index and the length. For the array of four ages below, index 4 gives Index 4 out of bounds for length 4, and index -1 gives Index -1 out of bounds for length 4. When an exception isn't caught, it ends the program, and any lines after it never run.
The buggy code
The lesson wants to print the last element of ages:
val ages = Array(14, 32, 50, 21)
println(ages(ages.length))
It throws ArrayIndexOutOfBoundsException instead of printing 21. The expression ages.length is 4, the number of elements. But the four elements sit at indexes 0, 1, 2 and 3, so index 4 is one position past the end.
The fix
- Find the index the code uses. It is
ages.length, which is 4. - Find the index the code needs. The last element of a four-element array is at index 3, which is
length - 1. - Subtract 1. Change the index to
ages.length - 1.
val ages = Array(14, 32, 50, 21)
println(ages(ages.length - 1))
This prints 21. It also keeps working if you change the array to hold more or fewer numbers, which a hard-coded ages(3) would not.
Avoiding the error in general
- Loop up to the length, not through it.
for (i <- 0 until ages.length)stops at the last valid index.0 to ages.lengthincludes the length and fails on the last pass. - Check before reading. When an index comes from elsewhere, test
i >= 0 && i < ages.lengthfirst. - Ask for an
Option.ages.lift(4)returnsNonefor an invalid index, andSome(value)for a valid one.ages.lift(4).getOrElse(0)gives 0 here, without an exception. - Catch the exception with
try/catchwhen failing is acceptable, as incase e: ArrayIndexOutOfBoundsException => .... Checking the index first is usually the better choice, since an out-of-range index normally means the code has a bug.
Common mistakes
- Treating length as the last index. This is the bug in the lesson, and it is the most common form of the error.
- Counting from 1. The third element is at index 2. Asking for index 3 in a three-element array fails.
- Empty arrays. For an empty array,
length - 1is -1, and reading index -1 throws as well. Index -1 does not mean "the last element" in Scala, unlike in some other languages. - Writing past the end. Assigning to an invalid index, such as
ages(4) = 1, throws the same exception. A fixed-size array can't grow.
Reading the stack trace
When the exception ends the program, the message starts with the exception's name, followed by the details. Compare the two numbers in Index 4 out of bounds for length 4. If the index equals the length, you are one past the end, which usually means a missing - 1. If the index is negative, a subtraction went too far, often from an empty array. Reading those two numbers first usually shows the cause faster than rereading the whole program.
Key takeaways
- Valid indexes are
0tolength - 1. - An invalid index throws
ArrayIndexOutOfBoundsExceptionat run time. - The last element is
array(array.length - 1).
