Room 3 of 15
· about 30 minutes
Reading and writing one slot
Room 2 gave you an array with slots in it. This room is how you get a value
out of a slot and put a value into one.
Tasks checked0 of 4
XP earned on this path0
Room complete
Notes
Bracket indexing
a[i] is the array access expression: it reads (or writes) the element at index i of the array a. The brackets are the operator; they bind tighter than almost everything else in Java (JLS §15.10.3).
final int[] nums = {10, 20, 30, 40, 50};
int first = nums[0]; // read: 10
int third = nums[2]; // read: 30
nums[4] = 99; // write: nums is now {10, 20, 30, 40, 99}
nums[1] = nums[1] + 1; // read-modify-write: nums[1] becomes 21
Three observations:
1. Indices are zero-based. The first element is nums[0]; the last is nums[nums.length - 1]. There is no nums[1] for the first element and no nums[nums.length] for the last. Zero-based indexing is shared with C, C++, Python, and almost every modern language; the exception (Lua) is uncommon. 2. The same expression reads on the right and writes on the left. x = nums[i] reads the slot. nums[i] = x writes the slot. Java does not distinguish the two with different syntax; the position in the assignment statement determines the operation. 3. The index expression can be any int-valued expression. nums[i], nums[2 * i + 1], nums[a.length - 1], nums[sc.nextInt()] are all legal. The expression is evaluated first, then the array slot is accessed at that index. If the result is outside [0, nums.length - 1], ArrayIndexOutOfBoundsException is thrown at runtime, which the treatment of that exception covers.
In other languages
- Python:
lst[i] is identical syntax; negative indices count from the end (lst[-1] is the last element). Java has no negative indexing: nums[-1] throws. - C:
a[i] is exactly *(a + i); the brackets are syntactic sugar over pointer arithmetic, and there is no bounds check. Out-of-range access is undefined behavior, not an exception. - JavaScript:
arr[i] returns undefined (not an error) when i is out of range. The lack of a runtime check is a common source of silent bugs.
length is a field, not a method
The size of a Java array is read with a.length. No parentheses. JLS §10.7 specifies that every array type has a single public final field named length whose type is int and whose value is the number of elements. The field is set when the array is constructed and is immutable for the life of the array.
final int[] xs = new int[5];
System.out.println(xs.length); // 5, correct
// System.out.println(xs.length()); // compile error: cannot find symbol: method length()
The same word, with parentheses, is the right form for several other Java types:
String s = "hi";
List<Integer> list = List.of(1, 2);
s.length(); // method, length 2 : note the parens
list.size(); // method, size 2 : not even named "length"
There is no consistent rule across Java's containers. String has length(). ArrayList has size(). Arrays have length (no parens). The asymmetry has historical reasons (String predates Collection; arrays predate both) but you cannot reason your way to the right form: you have to know each type.
Why does the compiler accept xs.length()?
It does not. The compile error is cannot find symbol: method length(). Read it as "I looked for a method named length on int[] and there is none." When this error appears on an array, the fix is to delete the parens. When it appears on a String, the fix is the opposite: add the parens. The error message is identical in either direction; only the type tells you which fix to make.
In other languages
- C / C++: plain C arrays have no length field at all; the size is whatever the caller passes alongside. C++
std::array and std::vector use .size() (method). - Python:
len(lst) is a free function, not a method or a field. lst.length and lst.size would both be AttributeError. - JavaScript / TypeScript:
arr.length is a property (no parens): same surface as Java. Helpful for students with prior JS background; less helpful for prior Python.
ArrayIndexOutOfBoundsException
When a[i] runs with i < 0 or i >= a.length, the JVM throws java.lang.ArrayIndexOutOfBoundsException at runtime (JLS §15.10.3). The check is performed on every array access: it is not an opt-in feature and cannot be disabled. The exception is unchecked (a subclass of RuntimeException), so no throws clause or catch block is required.
final int[] xs = new int[3]; // legal indices: 0, 1, 2 (length is 3)
int v = xs[3]; // ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
Message format (Java 11+): Index <i> out of bounds for length <n>. The number after Index is the offending index; the number after length is the array's size. Both numbers come straight from the access expression and array; reading them tells you exactly which slot was tried and how big the array actually was.
Where the exception fires
The exception fires at the access, not at the array's creation. new int[-1] throws a different exception: NegativeArraySizeException at construction. new int[3] allocates a 3-slot array with valid indices 0, 1, 2; the moment a piece of code touches xs[3] or xs[-1], the access expression throws. The bug is visible where the access happens, not where the bad index was computed, which is sometimes far from where it was set.
The "fence-post" off-by-one
The most common cause is a loop that uses <= where it should use <:
for (int i = 0; i <= xs.length; i++) { // bug: should be i < xs.length
System.out.println(xs[i]);
}
On the final iteration i equals xs.length, the access xs[xs.length] is out of range, and the JVM throws. The fix is in the loop bound, not in the body. The rule that a forward traversal tests i < a.length covers this in detail.
In other languages
- C / C++: no bounds check. Reading or writing past the end is undefined behavior: historically the source of buffer-overrun exploits. Java's bounds-check exists exactly to prevent that whole class of security issue.
- Python: raises
IndexError: Java's analog, just a different name and unchecked (no checked/unchecked distinction). - Rust: index access on a
Vec/slice panics by default (similar to a JVM exception); the get method returns Option<&T> instead for safe access.
Optional challenge
This one is optional. Do what the room says you can do, without opening any answers, then read the two traps below and check your work against them. Each trap is copied from the notes for this room.
Student can identify whether a Java expression for "size" needs parentheses or not by naming the receiver's type (int[]/double[]/etc → .length field; String → .length() method; ArrayList<E> → .size() method), and explain the source of the asymmetry.
- writing
xs.length() because every other length is a method - assuming
length is mutable