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

What this room checks you can do

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.

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.

Tasks

Do each one, then check the box. Checking a box is you saying you did it. You can uncheck a box if you check it by accident.

  1. trace
    Show the answer

    42.

  2. trace
    Show the answer

    {3, 2, 2}.

  3. trace
    Show the answer

    6.

  4. debug
    Show the answer

    drop the parens from xs.length.

Self check

Type what you think the answer is. Getting it wrong costs nothing and you can try as many times as you want.

Given int[] xs = new int[5]; xs[xs.length] = 99;, predict the runtime outcome.

Practice, untimed

Open this whenever you want, before the tasks or after them. Nothing in this section is recorded and nothing here is timed.

  1. Given int[] xs = {10, 20, 30}; int i = -1; System.out.println(xs[i]);, predict the runtime outcome.trace
    Show the answer

    ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3.

  2. Given the exception message Index 7 out of bounds for length 5, report (a) the array's size, (b) the index that was tried.trace
    Show the answer

    (a) 5, (b) 7.

  3. Correct the loop for (int i = 0; i <= xs.length; i++) System.out.println(xs[i]); to avoid the exception.write
    Show the answer

    change <= to <.

  4. Write the loop header for a counted traversal of int[] xs.write
    Show the answer

    for (int i = 0; i < xs.length; i++): xs.length, no parens.

  5. Given int[] xs = new int[3]; xs[xs.length - 1] = 99;, report xs[xs.length - 1].trace
    Show the answer

    99.

  6. Given int[] xs = new int[10]; System.out.println(xs.length());, predict the compile/runtime outcome.trace
    Show the answer

    compile error: cannot find symbol: method length().

  7. Given a compile error message cannot find symbol: method length(), identify what the receiver's type must be for the fix to be "delete the parens" vs "the type is wrong."trace
    Show the answer

    array type (int[], double[], etc.) for the first; method-typed receiver (e.g., a String) is irrelevant here.

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.

How this room finishes

This room is done when all four tasks are checked and the self check is answered.