Storing many values of one type in a single variable: creating arrays, selecting elements with the bracket operator, why printing an array gives you an address, the difference between copying a reference and copying an array, and the three traversal patterns you will use for the rest of the book. Follows Think Java 2e, Chapter 7 (Arrays and References), Sections 7.1-7.5, pp. 107-115, cross-referenced against The Java Tutorials — Arrays.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 7 · Arrays and References
Sections 7.1-7.5 · pp. 107-115
Objectives
This lesson follows Think Java 2e, Chapter 7 (Arrays and References), Sections 7.1-7.5, pp. 107-115. Everything on these slides can be checked against those pages.
1. Declare an array variable and create an array with new, or with an initializer list.
2. Select and assign elements with the bracket operator, and say what the valid indexes are.
3. Explain what an array variable actually holds, and draw the reference as an arrow.
4. Distinguish copying a reference from copying an array, and define aliasing.
5. Use a.length correctly, and say why it has no parentheses when String.length() does.
6. Write the three traversal patterns: transform every element, search for one, and reduce to a single value.
Warm-up
A problem from the last chapter that arrays exist to solve.
Discussion prompt
To check whether every letter in a string appears exactly twice, one approach loops through the string 26 times — once per letter. For a 3-million-character book, roughly how many steps is that? What would you need in order to do it in one pass?
Hint: 26 times 3 million.
Answer:
About 80 million steps. Doing it in one pass would need 26 separate counters — one per letter — updated as you go.
But who wants to declare 26 variables? That is exactly where arrays come in: a single variable that stores 26 integers, with arithmetic to pick out the right one. The end of the next lesson writes that program.
Concept
Up to now every variable has held one value. An array holds a sequence of values of the same type, each identified by an index — which is what makes it possible to write programs that manipulate larger amounts of data.
Figure (svg): An int array of four elements, all initialised to zero, with indexes 0 to 3 underneath
new, and the initializer list[], and what the variable really holdsDowney & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 7 (Arrays and References), Sections 7.1-7.5, pp. 107-115 — Chapter 7 opens on printed page 107.
Section
Section 7.1
Concept
An array is a sequence of values; the values are called elements. You can make an array of ints, doubles, Strings or any other type — but all the values in an array must have the same type.
int[] counts; // declare an int array variable
double[] values;
counts = new int[4]; // create the array itself
values = new double[size];
int[] counts = new int[4]; // or both in one line| part | what it does |
|---|---|
| int[] | the type: an array of ints — note the square brackets |
| counts | the variable name |
| new int[4] | allocates memory for four ints and initialises them all to zero |
| new double[size] | the size can be any int expression, decided when the array is created |
array — A collection of values in which all the values have the same type, and each is identified by an index.
element — One of the values in an array.
Array types look like other Java types, except they are followed by square brackets. The new operator — which you first met creating a Scanner — allocates the memory and automatically initialises all the elements to zero.
Notation
The declaration and the creation are separate steps, and there is a shorthand that does both with specific values.
Annotate
int[] a; makes a variable that could refer to an array. Until you assign one, it refers to nothing.new int[4] allocates the memory and zeroes it. Every element starts at 0 for numeric types — you never get whatever happened to be in memory.{1, 2, 3, 4} creates an array of four elements and fills them. The size is however many values you listed.The brace form only works in a declaration. Once the variable exists you must use new — which is a restriction worth knowing before you meet it.
Worked example
The size does not have to be a literal. That is what makes arrays useful for data whose amount you do not know when you write the program.
Scanner in = new Scanner(System.in);
System.out.print("How many scores? ");
int size = in.nextInt();
int[] scores = new int[size];
System.out.println("Created an array of " + scores.length);| the user types | size | array created | scores.length |
|---|---|---|---|
| 5 | 5 | five elements, all 0 | 5 |
| 0 | 0 | an empty array — legal | 0 |
| -1 | -1 | NegativeArraySizeException | — |
Read the size before creating the array.
Why: new int[size] uses whatever value size holds at that moment.
Note that the size is fixed from then on.
Why: An array cannot grow. If you need more room you create a new, larger array and copy.
Note that zero is allowed.
Why: An empty array is a perfectly good array — it just has no elements to index.
Note that negative is not.
Why: NegativeArraySizeException at run time. Validating the input first, as in Lesson 5b, would prevent it.
Verify: Enter 5 and confirm the length is 5; enter 0 and confirm it is 0 with no error.
Why: Then enter -1 and read the exception. The fix is the guard clause from Lesson 5b — check the value before using it, because the compiler cannot know what the user will type.
Prediction
new initialises them for you.
int[] a = new int[3];| index | value |
|---|---|
| 0 | 0 |
| 1 | 0 |
| 2 | 0 |
Predict first
What does the array contain?
Correct: Three zeros
Why: The new operator allocates memory for the array and automatically initialises all its elements to zero. Java never leaves you reading whatever happened to be in memory, which is a guarantee some older languages do not make. For an array of objects the initial value would be null instead.
Concept
new initialises the elements for you. What 'zero' means depends on the type, and it is worth knowing the whole set now.
| array type | elements start as |
|---|---|
| int[], long[] | 0 |
| double[] | 0.0 |
| char[] | the character with code point 0 |
| boolean[] | false |
| String[] and any object array | null — the subject of Chapter 9 |
The first four are convenient: a counter array is ready to count the moment it is created, which is exactly what the histogram in the next lesson relies on. The last row is different in kind, and it is where Chapter 9's null keyword becomes important.
Trap
The variable exists; the array does not.
int[] counts;
counts[0] = 7; // no array to put it in| statement | what exists | outcome |
|---|---|---|
| int[] counts; | a variable that could refer to an array | no array yet |
| counts[0] = 7; | still no array | compile error: variable counts might not have been initialized |
This is Lesson 2a's uninitialised-variable error in a new setting. Java catches it at compile time, which is fortunate — the alternative would be writing into memory that was never allocated.
Declare and create, ideally in one statement.
int[] counts = new int[4];
counts[0] = 7;| statement | what exists |
|---|---|
| int[] counts = new int[4]; | a variable AND a four-element array it refers to |
| counts[0] = 7; | element 0 becomes 7 |
The distinction between the variable and the array is not pedantry — it is the entire subject of Section 7.4, and getting it clear now makes aliasing straightforward instead of mysterious.
Definition probe
The size must be a non-negative int expression.
Sort into buckets
Sort each attempt.
Fill the middle
Declare and create in one statement.
Fill in the blanks
double[] values = new double[10];
Why: The type is double[] — the element type followed by square brackets — and new allocates the memory and initialises all ten elements to 0.0. Without new you would have declared a variable that refers to no array at all.
Socratic
It is a restriction. Ask what it buys.
Discussion prompt
An array can hold ints or doubles or Strings, but never a mixture. What does that restriction let the compiler and the machine do that a mixed collection would not?
Hint: Think about how the machine finds element number 500.
Answer:
Because every element is the same size, the machine can compute the location of element i by arithmetic — start of the array, plus i times the element size. That is why a[500] is as fast as a[0].
It also lets the compiler check your code: it knows a[i] is an int, so it can reject String s = a[i]; before the program runs. A mixed collection could not offer either guarantee.
This is the same bargain Java makes everywhere — say what type you mean, and get checking and speed in return. Java does have collections that hold mixed types, and they pay for it in both.
Section
Section 7.2
Concept
The [] operator selects elements from an array, and you can use it anywhere in an expression — on the right of an assignment to read, or on the left to write.
counts[0] = 7;
counts[1] = counts[0] * 2;
counts[2]++;
counts[3] -= 60;
System.out.println("The zeroth element is " + counts[0]);| statement | element changed | new value |
|---|---|---|
| counts[0] = 7; | 0 | 7 |
| counts[1] = counts[0] * 2; | 1 | 14 |
| counts[2]++; | 2 | 1 |
| counts[3] -= 60; | 3 | -60 |
Every operator you know works on an element: assignment, arithmetic, ++, -=. An element behaves exactly like an ordinary variable of that type — because that is essentially what it is.
Picture it
The bold numbers inside the boxes are the elements; the lighter numbers outside are the indexes used to identify each location.
Figure (svg): An int array holding 7, 14, 1 and -60 with indexes 0 to 3 marked underneath
As with strings, the index of the first element is 0, not 1 — which is why the first element is sometimes called the zeroth element. For this array the only legal indexes are 0, 1, 2 and 3.
Worked example
One of the most common ways to index an array is with a loop variable. In this context the name i is short for index.
int i = 0;
while (i < 4) {
System.out.println(counts[i]);
i++;
}
// usually written as a for loop:
for (int i = 0; i < 4; i++) {
System.out.println(counts[i]);
}| i | condition | counts[i] |
|---|---|---|
| 0 | true | 7 |
| 1 | true | 14 |
| 2 | true | 1 |
| 3 | true | -60 |
| 4 | false — loop ends | — |
Start the index at 0.
Why: The first element is the zeroth.
Loop while the index is less than the length.
Why: The body runs only when i is 0, 1, 2 or 3 — exactly the legal indexes.
Use i as the index each pass.
Why: counts[i] displays the ith element.
Prefer the for form.
Why: It puts the initialisation, condition and update in one place, exactly as in Lesson 6a.
Verify: Expect 7, 14, 1, -60 on separate lines.
Why: This is the same i < n pattern as the string loop in Lesson 6b, for the same reason: n elements have indexes 0 to n-1, so a strict less-than visits each exactly once.
Prediction
Elements behave like ordinary variables.
int[] counts = new int[4];
counts[0] = 5;
counts[1] = counts[0] * 3;
counts[1]++;| statement | counts[1] |
|---|---|
| new int[4] | 0 |
| counts[1] = counts[0] * 3 | 15 |
| counts[1]++ | 16 |
Predict first
What is counts[1]?
Correct: 16
Why: counts[0] is 5, so counts[0] * 3 is 15, and the increment then makes it 16. An array element supports every operator an ordinary variable does — assignment, arithmetic, ++ — because it is simply a named storage location reached by index rather than by name.
Concept
This is the most important sentence in the chapter: the value of counts is a reference to the array. You should think of the array and the variable that refers to it as two different things.
Figure (svg): A variable box labelled counts with an arrow pointing to a separate four-element array object
The arrow is the point. The variable is a box holding a reference; the array is somewhere else entirely. As you will see in Section 7.4, another variable can be made to refer to the same array, and counts can be made to refer to a different one.
Trap
Any index outside 0 to length-1 throws.
int[] counts = new int[4];
counts[4] = 1; // there is no element 4
counts[-1] = 1; // and no element -1| index used | legal range | outcome |
|---|---|---|
| 4 | 0 to 3 | ArrayIndexOutOfBoundsException |
| -1 | 0 to 3 | ArrayIndexOutOfBoundsException |
| 3 | 0 to 3 | fine — the last element |
Both compile. The compiler cannot know what value an index expression will have at run time, so this is a run-time error — and the exception message names the offending index, which usually identifies the bug immediately.
Legal indexes are 0 to length - 1, and the loop condition is what keeps you inside them.
for (int i = 0; i < counts.length; i++) {
counts[i] = 0; // always in range
}| for an array of length n | value |
|---|---|
| first index | 0 |
| last index | n - 1 |
| number of elements | n |
| loop condition | i < n |
Exactly the same table as the string ruler in Lesson 6b, with length in place of length(). One idea, two places — which is why getting it right once is worth the effort.
Prediction
An array of length 4.
int[] a = new int[4];
// which of these throws?
a[0] a[3] a[4] a[-1]| index | legal? |
|---|---|
| 0 | yes — the first element |
| 3 | yes — the last element |
| 4 | no — one past the end |
| -1 | no — indexes are never negative |
Predict first
Which indexes throw ArrayIndexOutOfBoundsException?
Correct: a[4] and a[-1]
Why: For an array of length 4 the legal indexes are 0, 1, 2 and 3, so both 4 and -1 are out of range. Note that a[3] is fine — it is the last element, at index length minus one, which is the boundary people most often get wrong in the other direction.
Fill the middle
The standard traversal loop.
Fill in the blanks
for (int i = 0; i < counts.length; i++) ___
Why: i < counts.length visits indexes 0 through length-1, which is every element exactly once. Note that length for an array has no parentheses — unlike String.length(), it is a constant rather than a method, which is one of Java's genuinely confusing inconsistencies.
Explain it to yourself
It causes endless off-by-one errors. Ask what it buys.
Discussion prompt
Indexes start at 0 rather than 1, which is the source of most array bugs. What does zero-based indexing make simpler? Think about how the machine locates element i.
Hint: Where is element 0, relative to the start of the array?
Answer:
Element i lives at start + i times the element size. With zero-based indexing element 0 is at the start with no adjustment; with one-based indexing every access would need a subtraction.
It also makes the loop condition clean: i < length visits exactly length elements, and the same pattern works for strings, arrays and everything else. One-based indexing would need i <= length and would break the correspondence with the count.
The cost is real — the last index is length-1, and that catches everyone. The compensation is that every index expression is one subtraction simpler, in a language where indexing happens constantly.
Section
Section 7.3
Concept
You can pass an array to println, but it probably does not do what you would like.
int[] a = {1, 2, 3, 4};
System.out.println(a);
// output is something like: [I@bf3f7e0| part of the output | meaning |
|---|---|
| [ | the value is an array |
| I | of integers |
| @ | separator |
| bf3f7e0 | the address of the array in memory, in hexadecimal |
This is the same shape as printing System.out in Lesson 3a, and for the same reason: what you are printing is a reference, so what you see is where the thing lives rather than what it contains.
Picture it
Once you have the picture from the previous idea, the strange output stops being strange. println is printing the box, not the array.
Figure (svg): A variable a holding a reference arrow to a four-element array, with a note that println prints the reference
To display the elements you have to visit them yourself — or use a library method that does. Both are worth knowing, and the book shows them in that order deliberately.
Worked example
If we want to display the elements, we can do it ourselves. Note the small detail that makes the commas come out right.
public static void printArray(int[] a) {
System.out.print("{" + a[0]);
for (int i = 1; i < a.length; i++) {
System.out.print(", " + a[i]);
}
System.out.println("}");
}| step | output so far |
|---|---|
| print("{" + a[0]) | {1 |
| i = 1: print(", " + a[1]) | {1, 2 |
| i = 2 | {1, 2, 3 |
| i = 3 | {1, 2, 3, 4 |
| println("}") | {1, 2, 3, 4} |
Print the opening brace and the first element together.
Why: Element 0 is handled outside the loop, which is why the loop starts at 1.
Loop from 1, printing a comma and space before each element.
Why: The separator goes BEFORE each element from the second onwards, so there is never a trailing comma.
Use a.length rather than a literal.
Why: The method then works for arrays of any size.
Close with the brace and a newline.
Why: println rather than print, so the line is finished.
Verify: For {1, 2, 3, 4} expect exactly {1, 2, 3, 4} with no trailing comma.
Why: Then think about what happens for an empty array: a[0] throws, because there is no element 0. Handling that would need a guard — a good illustration that a method which works on your test data is not necessarily correct.
Prediction
a is an int array of four elements.
int[] a = {1, 2, 3, 4};
System.out.println(a);| what is passed | what println shows |
|---|---|
| the array variable | the value it holds — a reference |
| the reference | type and address, like [I@bf3f7e0 |
Predict first
What appears?
Correct: Something like [I@bf3f7e0 — the type and address
Why: The variable holds a reference to the array, so that is what println displays: [ for array, I for int, and the memory address in hexadecimal. The address differs on each run, exactly as it did when you printed System.out in Lesson 3a — and for the same reason.
Concept
The Java library includes a class, java.util.Arrays, providing methods for working with arrays. One of them returns a string representation.
import java.util.Arrays;
int[] a = {1, 2, 3, 4};
System.out.println(Arrays.toString(a));
// [1, 2, 3, 4]| approach | output | effort |
|---|---|---|
| println(a) | [I@bf3f7e0 | none, and useless |
| your own printArray | {1, 2, 3, 4} | a method to write and maintain |
| Arrays.toString(a) | [1, 2, 3, 4] | one line, and an import |
Notice that Arrays.toString uses square brackets instead of curly braces. But as Think Java says, it beats writing your own printArray method — and it handles the empty array correctly, which the hand-written version above does not.
Trap
The same problem, harder to spot — because the message around it looks fine.
int[] a = {1, 2, 3};
System.out.println("The array is " + a);
// The array is [I@bf3f7e0| part | how it prints |
|---|---|
| "The array is " | as written |
| a | converted to a string — which for an array is its address |
Concatenation converts the array to a String, and an array's string form is its type and address. The surrounding text makes the output look deliberate, which is why this version survives longer than a bare println.
Convert the array explicitly with Arrays.toString.
import java.util.Arrays;
int[] a = {1, 2, 3};
System.out.println("The array is " + Arrays.toString(a));
// The array is [1, 2, 3]| written | output |
|---|---|
| "..." + a | the address |
| "..." + Arrays.toString(a) | the elements |
A useful habit: if output contains an @ followed by hexadecimal, you printed a reference. That one recognition covers arrays, and every object you meet from Chapter 9 onwards.
Matching
Three ways to display the array {1, 2, 3}.
Match the pairs
Why: println shows the reference; the hand-written method uses curly braces because the book prefers them; and Arrays.toString uses square brackets. The difference between the last two is purely cosmetic — the real difference is that one of them is already written and tested.
Error analysis
The loop begins at index 1, not 0, which looks like an off-by-one and is not.
Annotate
{1, 2, 3, 4,} would be the result of putting it after.i = 0 pattern, and the comment-free code does not say so.a[0] throws ArrayIndexOutOfBoundsException when there is no element 0, and nothing in the method guards against it.Reading a method and asking what input would break this? is a habit worth building. The empty array is the classic answer for anything that touches element 0.
Real world
You have now seen this output shape twice.
Discussion prompt
Printing System.out in Lesson 3a gave java.io.PrintStream@685d72cd, and printing an array gives [I@bf3f7e0. What do those two have in common, and what does it predict about printing other things later in the book?
Hint: What kind of value is each variable holding?
Answer:
Both variables hold references to objects, and Java's default way of turning an object into text is its type followed by @ and its address.
It predicts that every object you create from Chapter 10 onwards will print this way by default — a Point, a Time, a Card. Chapter 11 shows how to fix that by writing a toString method, which is exactly what Arrays.toString is doing for arrays.
The recognition to keep: an @ and some hexadecimal means you printed the reference, not the contents. That single observation will save you a puzzled minute many times over.
Section
Section 7.4
Concept
Array variables contain references to arrays. When you make an assignment to an array variable, it simply copies the reference — it does not copy the array itself.
double[] a = new double[3];
double[] b = a;
a[0] = 17.0;
System.out.println(b[0]); // 17.0| statement | arrays in existence | what a and b refer to |
|---|---|---|
| new double[3] | one | a refers to it |
| double[] b = a; | still one | both a and b refer to it |
| a[0] = 17.0; | one | the change is visible through both |
| println(b[0]) | — | 17.0 |
alias — One of two or more variables that refer to the same object. Changes made through one are visible through the others.
Any change made through either variable will be seen by the other. Because a and b are different names for the same thing, they are called aliases.
Picture it
This is the picture the rest of the book depends on. Two boxes, two arrows, one array.
Figure (svg): Two variables a and b each with an arrow pointing to the same three-element array
Compare this against Lesson 2a's int a = 5; int b = a;, where changing a left b alone. The difference is entirely that an array variable holds a reference rather than a value — and that difference is the whole subject of Chapters 9 and 10.
Worked example
If you want to copy the array rather than the reference, you have to create a new array and copy the elements across.
// by hand:
double[] b = new double[a.length];
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
// or with the library:
double[] b = Arrays.copyOf(a, a.length);| after | arrays in existence | a[0] = 17.0 changes |
|---|---|---|
| double[] b = a; | 1 | both a[0] and b[0] |
| the copy loop | 2 | only a[0] |
| Arrays.copyOf | 2 | only a[0] |
Create a second array of the right size.
Why: new double[a.length] — using the length rather than a literal so it works for any array.
Copy the elements one at a time.
Why: The loop reads from a and writes to b; both are ordinary element assignments.
Or use the library.
Why: Arrays.copyOf(a, a.length) does exactly this. Its second parameter is the number of elements to copy, so it can also copy part of an array.
Confirm they are now independent.
Why: Changing a[0] leaves b[0] alone, because they are different arrays.
Verify: Set a[0] = 17.0 after copying and check that b[0] is still 0.0.
Why: That single check is what distinguishes a copy from an alias, and it is worth running deliberately the first time — the two situations look identical in the source and behave completely differently.
Prediction
Assignment between array variables.
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]);| step | arrays | a[0] |
|---|---|---|
| int[] b = a; | one, with two names | 1 |
| b[0] = 99; | one | 99 — the same array |
Predict first
What is displayed?
Correct: 99
Why: int[] b = a; copies the reference rather than the array, so a and b are aliases for the same array and a change through either is visible through the other. Compare this with int x = 5; int y = x; from Lesson 2a, where changing y left x alone — the difference is entirely that an array variable holds a reference.
Concept
All arrays have a built-in constant, length, storing the number of elements. This is a genuine inconsistency in Java, and it catches everyone.
Figure (svg): A rule card contrasting the array length constant with the String length method
| written | for | correct? |
|---|---|---|
| a.length | an array | yes |
| a.length() | an array | no — cannot find symbol |
| s.length() | a String | yes |
| s.length | a String | no |
The expression a.length may look like a method invocation, but there are no parentheses and no arguments. The last time a traversal loop executes, i is a.length - 1, which is the index of the last element — and when i equals a.length the condition fails, which is a good thing, because a[a.length] would throw.
Trap
One array, two names. The 'backup' changes along with the original.
double[] original = {1.0, 2.0, 3.0};
double[] backup = original; // NOT a copy
original[0] = 99.0;
System.out.println(backup[0]); // 99.0 - the backup changed too| step | arrays | backup[0] |
|---|---|---|
| double[] backup = original; | 1 | 1.0 |
| original[0] = 99.0; | 1 | 99.0 — through the alias |
There was never a backup. The assignment copied the reference, so both names point at the one array — and the original values are gone for good.
Create a second array.
double[] original = {1.0, 2.0, 3.0};
double[] backup = Arrays.copyOf(original, original.length);
original[0] = 99.0;
System.out.println(backup[0]); // 1.0 - genuinely unaffected| step | arrays | backup[0] |
|---|---|---|
| Arrays.copyOf(...) | 2 | 1.0 |
| original[0] = 99.0; | 2 | 1.0 — a different array |
The test that tells them apart: change one and look at the other. If both changed, you have an alias; if only one did, you have a copy. Everything else about the two situations looks the same in the source.
Definition probe
Ask how many arrays exist afterwards.
Sort into buckets
Sort each pair of statements.
Invariant
Step through an aliasing example and watch how many arrays exist.
Step through it
At which frame does the number of arrays change from one to two?
The fourth. Until copyOf runs there is exactly one array however many variables name it — and counting arrays rather than variables is the habit that makes aliasing predictable.
Edge cases
Reassigning a variable is different from changing an element.
Discussion prompt
After int[] b = a;, what is the difference between writing b[0] = 99; and writing b = new int[3];? Which one affects a, and why?
Hint: One changes the array; the other changes which array b points at.
Answer:
b[0] = 99; changes the array that both variables refer to, so a sees it. b = new int[3]; changes what b refers to, leaving a pointing at the original array, which is unaffected.
So the arrow and the boxes are separate things you can change independently. Assigning to b moves the arrow; assigning to b[i] changes a box the arrow points at.
This distinction is exactly what Chapter 10 calls mutability, and it is why the book insists you think of the array and the variable that refers to it as two different things. Getting it now makes three later chapters straightforward.
Section
Section 7.5
Concept
Many computations can be implemented by looping through the elements of an array and performing an operation on each. This is a traversal, and the simplest kind transforms every element in place.
int[] a = {1, 2, 3, 4, 5};
for (int i = 0; i < a.length; i++) {
a[i] *= a[i];
}
// a is now {1, 4, 9, 16, 25}| i | a[i] before | a[i] after |
|---|---|---|
| 0 | 1 | 1 |
| 1 | 2 | 4 |
| 2 | 3 | 9 |
| 3 | 4 | 16 |
| 4 | 5 | 25 |
Note that this modifies the array in place rather than building a new one — which works because a[i] on the left of an assignment writes into the array the variable refers to.
Picture it
Each element replaced by its own square, in one pass.
Figure (svg): An array of five elements holding 1, 4, 9, 16 and 25 after each was squared in place
This is the first of three traversal shapes. The other two do not change the array at all — one looks for something in it, and one reduces it to a single value.
Worked example
A search traverses an array looking for a particular element, and returns where it found it. This code is essentially what String.indexOf does.
public static int search(double[] array, double target) {
for (int i = 0; i < array.length; i++) {
if (array[i] == target) {
return i;
}
}
return -1; // not found
}| i | array[i] | equals 1.23? | action |
|---|---|---|---|
| 0 | 3.14 | no | continue |
| 1 | -55.0 | no | continue |
| 2 | 1.23 | yes | return 2 |
| — | — | — | the last line is never reached |
Traverse the array with an index loop.
Why: You need the index, because the index is what you are going to return.
Return immediately on a match.
Why: There is no reason to keep looking, and returning from inside the loop is the clearest way to say so.
Return -1 if the loop finishes.
Why: Reaching the line after the loop means every element was checked and none matched. -1 is a special value chosen to indicate a failed search.
Note why -1 is safe.
Why: Indexes are never negative, so it cannot be confused with a real answer — exactly as in Lesson 6b's indexOf.
Verify: For {3.14, -55.0, 1.23, -0.8} searching for 1.23, expect 2 — because array indexes start at 0.
Why: Then search for a value that is not present and confirm you get -1. And note the caution from Lesson 2b: comparing doubles with == is unreliable in general, which makes this method a better illustration than an implementation.
Definition probe
Three shapes: transform every element, search for one, reduce to a single value.
Sort into buckets
Sort each task.
Concept
Another common traversal is a reduce operation, which reduces an array of values to a single value — a sum, a product, a minimum, a maximum.
public static double sum(double[] array) {
double total = 0.0;
for (int i = 0; i < array.length; i++) {
total += array[i];
}
return total;
}| i | array[i] | total after |
|---|---|---|
| before | — | 0.0 |
| 0 | 3.0 | 3.0 |
| 1 | 4.0 | 7.0 |
| 2 | 5.0 | 12.0 |
traversal — Looping through the elements of an array.
accumulator — A variable used to accumulate a running total or other combined result during a traversal.
Before the loop, total is initialised to 0. Each pass updates it by adding one element, and at the end it contains the sum. A variable used this way is called an accumulator, because it accumulates the running total.
Trap
The accumulator is reset every pass, so it only ever holds the last element.
public static double sum(double[] array) {
for (int i = 0; i < array.length; i++) {
double total = 0.0; // inside the loop
total += array[i];
}
return total; // and out of scope here
}| i | total at the start of the pass | total at the end |
|---|---|---|
| 0 | 0.0 — freshly declared | 3.0 |
| 1 | 0.0 — declared AGAIN | 4.0 |
| 2 | 0.0 | 5.0 |
Two things are wrong at once: the running total is destroyed on every pass, and total is out of scope at the return — which is Lesson 6a's point that a variable declared inside a block lives only in that block. The compile error actually saves you here.
Declare the accumulator before the loop, so it survives across passes.
public static double sum(double[] array) {
double total = 0.0;
for (int i = 0; i < array.length; i++) {
total += array[i];
}
return total;
}| where total is declared | survives the pass? | in scope at the return? |
|---|---|---|
| before the loop | yes | yes |
| inside the loop | no — recreated each pass | no |
The rule generalises: anything that has to remember something across passes must be declared outside the loop. Accumulators, counters, best-so-far values — all of them.
Prediction
The target is not in the array.
double[] array = {3.14, -55.0, 1.23};
int index = search(array, 9.9);| i | match? |
|---|---|
| 0 | no |
| 1 | no |
| 2 | no |
| loop ends | the line after it runs |
Predict first
What is index?
Correct: -1
Why: Every element is checked, none matches, and the loop finishes — so execution reaches the return statement after it, which returns -1. Because indexes are never negative, -1 can safely mean 'not found', and the caller must check for it before using the result as an index.
Fill the middle
Sum the elements of an int array.
Fill in the blanks
int total = 0;
for (int i = 0; i < a.length; i++) +=} a[i];
}
Why: The accumulator is initialised to 0 before the loop, so it survives across passes, and += adds each element to the running total. Declaring total inside the loop would reset it every pass and leave it out of scope afterwards.
Counterexample
Test the reduce pattern at its edge.
Discussion prompt
sum initialises total to 0.0 and adds every element. What does it return for an array with no elements — and is that the right answer? Now ask the same question for a method that finds the maximum.
Hint: The two have different answers, and that is the interesting part.
Answer:
sum returns 0.0, and that is exactly right: the sum of no numbers is zero, and the loop simply runs zero times.
Maximum has no such answer. There is no sensible maximum of no numbers, and the usual trick of initialising to zero is wrong — for an array of negative numbers it would return 0, which is not in the array at all.
So a reduce needs a starting value that is neutral for its operation — 0 for sum, 1 for product — and when no such value exists, the empty case has to be handled separately. This is the kind of edge case that separates a method that works on your test data from one that is correct.
Comparison
They share an indexing scheme and differ in two specific ways. Fill the blanks.
Comparison matrix
| String | array | |
|---|---|---|
| first index | 0 | 0 |
| last index | length() - 1 | length - 1 |
| how you get the size | s.length() — a method | a.length — a constant, no parentheses |
| can you change an element? | no — Strings are immutable (Chapter 9) | yes — a[i] = x |
| out-of-range access | StringIndexOutOfBoundsException | ArrayIndexOutOfBoundsException |
The third row is the inconsistency that catches everyone, and the fourth is the one that matters most: an array can be modified through any reference to it, which is what makes aliasing consequential.
Pattern
Three traversal shapes cover almost everything you will do with an array, and they differ in what they keep outside the loop.
// transform: change every element in place
for (int i = 0; i < a.length; i++) {
a[i] = f(a[i]);
}
// search: return the index, or -1
for (int i = 0; i < a.length; i++) {
if (a[i] == target) return i;
}
return -1;
// reduce: accumulate into one value
double total = 0.0; // OUTSIDE the loop
for (int i = 0; i < a.length; i++) {
total += a[i];
}
return total;| pattern | what lives outside the loop | what it returns |
|---|---|---|
| transform | nothing | nothing — the array is changed |
| search | nothing | an index, or -1 |
| reduce | the accumulator | the accumulated value |
a.length has no parentheses; s.length() does.i < a.length is what keeps you inside them.Check
Work it out before you click.
int[] a = {1, 2, 3};
int[] b = a;
int[] c = Arrays.copyOf(a, a.length);
a[0] = 99;| variable | refers to | element 0 after |
|---|---|---|
| a | the original array | 99 |
| b | the same array | 99 |
| c | a separate copy | 1 |
Check your understanding
What are b[0] and c[0]?
Answer: A
Why: b = a copies the reference, so b is an alias for the same array and sees the change. Arrays.copyOf creates a second array, so c holds an independent copy taken before the change and is unaffected.
Check
Work it out before you click.
int[] a = new int[5];
String s = "hello";| expression | legal? |
|---|---|
| a.length | yes — a constant |
| a.length() | no |
| s.length() | yes — a method |
| s.length | no |
Check your understanding
Which pair of expressions is correct?
Answer: A
Why: An array's length is a built-in constant with no parentheses, while a String's length is a method that requires them. There is no logic to the inconsistency — it simply has to be memorised, and it is one of the most common compile errors beginners hit.
a.length() treats the array's constant as a method, which does not compile.s.length treats the String's method as a field, which does not compile.Check
Work it out before you click.
int[] a = {10, 20, 30};
int total = 0;
for (int i = 0; i < a.length; i++) {
total += a[i];
}| i | a[i] | total after |
|---|---|---|
| 0 | 10 | 10 |
| 1 | 20 | 30 |
| 2 | 30 | 60 |
Check your understanding
What is total after the loop?
Answer: A
Why: The accumulator starts at 0 and each pass adds one element, so it ends holding the sum of all three: 60. Declaring total before the loop is what allows it to survive across passes — inside the loop it would be reset each time and out of scope afterwards.
total++ instead of total += a[i].Real world
Section 7.4 looks like a small technical point. It is the foundation of three later chapters and a large share of real bugs.
Discussion prompt
You pass an array to a method, and the method changes one of its elements. Does the caller see the change? Reason it out from what parameter passing does and what an array variable holds.
Hint: Lesson 4a said a method gets a copy of the argument. A copy of what, exactly?
Answer:
Yes, the caller sees it. Parameter passing copies the argument's value — and the value of an array variable is a reference. So the method gets its own variable pointing at the same array, and any element it changes is changed for everyone.
That is not a contradiction of Lesson 4a. A method still cannot change which array the caller's variable refers to — assigning a = new int[3] inside the method affects only the method's own copy of the reference. It can change the array's contents, not the caller's arrow.
This is the single most useful thing to take from Chapter 7. It is why Chapter 10 is called Mutable Objects, why Chapter 9 spends a whole chapter on immutable ones, and why a great deal of real debugging comes down to asking who else has a reference to this?
Commit first
Commit to an answer and to your confidence.
Predict first
After int[] a = {1, 2, 3}; int[] b = a; b[0] = 99; what is a[0]?
Correct: 99
Why: The assignment int[] b = a; copies the reference, not the array, so a and b are two names for the same array — aliases. Changing an element through b changes the one array, and a sees it. Compare this against int x = 5; int y = x; y = 99; from Lesson 2a, where x stays 5: the difference is entirely that a primitive variable holds a value while an array variable holds a reference. If you were confident the answer was 1, that is the most valuable misconception to have corrected before Chapter 9.
Explain it
Two minutes, drawing as you go.
Discussion prompt
A classmate made a 'backup' of an array with int[] backup = original; and is confused that editing the original also changed the backup. Draw the memory diagram and explain. Then show them the two ways to make a real copy.
Hint: Draw the boxes and the arrows separately, and count the arrays.
Answer:
Draw two variable boxes and one array. Both boxes have arrows to the same array — that is what the assignment did. It copied the arrow, not the boxes it points at. So there is only one array, and changing it through either name changes it for both.
To make a real copy you need a second array: either new int[original.length] and a loop that copies each element, or Arrays.copyOf(original, original.length), which does the same thing in one line.
If the drawing has two arrays before you get to copyOf, start again — the whole explanation depends on the listener seeing that there was only ever one.
Exit ticket
One question before you close the deck.
Predict first
What does an array variable actually hold?
Correct: A reference to the array, which lives elsewhere in memory
Why: You should think of the array and the variable that refers to it as two different things: the variable is a box holding a reference, and the array is somewhere else. That single fact explains everything surprising in this lesson — why printing the variable shows an address, why assigning one array variable to another creates an alias rather than a copy, and why a method can change the contents of an array it was passed. It is also the foundation of Chapters 9, 10 and 11.
Connect it up
One page, from memory.
Draw it
Draw the memory diagram for double[] a = new double[3]; double[] b = a; — two variable boxes and however many arrays actually exist. Then draw it again after b = Arrays.copyOf(a, 3); and mark what changed. Beside them, write the three traversal patterns — transform, search, reduce — and mark which one needs a variable declared outside the loop and why. Finally write a.length and s.length() and circle the difference.
Recap
Five sections that introduce the first data structure — and, more importantly, the first time a variable does not hold its own value.
| if you remember one thing | it is this |
|---|---|
| about array variables | the variable and the array are two different things |
| about copying | assignment copies the arrow, not the boxes |
| about traversal | the accumulator goes outside the loop |
new allocates the array and initialises every element to zero, 0.0, false or null.[] operator selects an element, and an element behaves like an ordinary variable.Arrays.copyOf.a.length is a constant with no parentheses, unlike s.length().Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.