Java Module 4 – Arrays and strings in depth
Arrays: creation, indexing and iteration
Create Java arrays with new and with initialisers, read and write them by index, loop with for and for-each, and copy them without aliasing bugs.
What you will learn
- Create arrays with new and with initialisers, and predict the default value of every new element
- Loop over an array with for and for-each without stepping past its last index
- Display an array's elements with Arrays.toString instead of its class name and hash code
- Copy an array with clone, Arrays.copyOf or System.arraycopy instead of sharing it by assignment
- Predict what a method call or a store through an Object[] does to an array
Before you start
What you’ll learn · Before you start
What you will learn
- Create arrays with new and with initialisers, and predict the default value of every new element
- Loop over an array with for and for-each without stepping past its last index
- Display an array's elements with Arrays.toString instead of its class name and hash code
- Copy an array with clone, Arrays.copyOf or System.arraycopy instead of sharing it by assignment
- Predict what a method call or a store through an Object[] does to an array
Before you start
On this page
Six students’ marks in six separate variables need six names, and every total, average or search has to mention all
six. An array keeps a fixed number of values of one type together in one object, numbered from 0:
int[] marks = new int[5]; creates five int elements that all start at 0, and
int[] marks = {71, 64, 88, 92, 57}; creates five with those values. The JVM checks every index you use, and an array
variable holds a reference to the array, so int[] b = a; gives one array a second name instead of copying it.
You will create arrays and loop over them without stepping past the end, step through what an assignment, a copy and a
method call do to one array, and meet the two errors arrays are known for. Every program is a
compact source file: A .java file whose fields and methods are written with no class declaration around them, such as a file that holds only a main method. JEP 512 made it a permanent part of Java in JDK 25. More in “Your first program: a compact source file”, run with java Name.java on JDK 25.
new int[5] fixes the length and fills in default values
Defaults.java creates arrays in three ways, prints what each new array holds before anything is stored in it, and
then asks for a negative length:
// Three ways to create an array, what a new array holds before anything is stored in it,
// and what happens when the length is negative.
void main() {
int[] marks = new int[5]; // five ints
double[] prices = new double[3];
boolean[] present = new boolean[4];
char[] initials = new char[2];
String[] names = new String[3]; // three references, no String objects yet
IO.println("marks " + Arrays.toString(marks) + ", length " + marks.length);
IO.println("prices " + Arrays.toString(prices));
IO.println("present " + Arrays.toString(present));
IO.println("initials " + (int) initials[0] + " and " + (int) initials[1]
+ " (character codes)");
IO.println("names " + Arrays.toString(names));
int[] days = {31, 28, 31, 30}; // an initialiser: the compiler counts the values
int[] none = {}; // an empty array, which is not the same as null
String[] cities;
cities = new String[] {"Pune", "Kochi"}; // outside a declaration, name the type
IO.println("days " + days.length + ", none " + none.length + ", cities " + cities.length);
int n = -1;
try {
int[] wrong = new int[n];
} catch (NegativeArraySizeException e) {
IO.println("new int[-1]: " + e);
}
} Output
marks [0, 0, 0, 0, 0], length 5 prices [0.0, 0.0, 0.0] present [false, false, false, false] initials 0 and 0 (character codes) names [null, null, null] days 4, none 0, cities 2 new int[-1]: java.lang.NegativeArraySizeException: -1
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Defaults.java
Version note
Compact source files and IO.println are final in JDK 25 (JEP 512), and such a file imports the whole java.base
module, which is why Arrays needs no import line. On JDK 21 or older, put the same code in a class with
public static void main(String[] args), write System.out.println instead of IO.println, add
import java.util.Arrays;, and declare the other methods static so that main can call them.
new int[5]fixes the length and fills in default values. The Java Language Specification gives every array element a default value: The value every element of a new array starts with, before anything is stored in it: 0 for the integer types, 0.0 for float and double, false for boolean, the character with code 0 for char, and null for every reference type. when the array is created, the one the table below lists for its type. A newString[3]therefore holds threenulls, not three empty strings: noStringobjects exist until you store some.- An initialiser lists the values, and the compiler counts them:
dayshas 4 elements. The short form{31, 28, 31, 30}works only in a declaration. Anywhere else, for example when you assign the variable later or pass an array straight to a method, write the type as well:new String[] {"Pune", "Kochi"}. lengthis a field, not a method, so it has no parentheses:marks.length, butname.length()for aString. It isfinal. Once an array exists its length never changes, so to make room for more elements you create a longer array and copy the old ones into it, asArrays.copyOfdoes further down, or use anArrayList, which does that for you and has a lesson in the collections module.- An empty array is not
null.nonehas length 0, and a loop over it simply runs zero times. A variable that holdsnullrefers to no array at all, and reading itslengththrowsNullPointerException. - A negative length is a run-time error. The compiler cannot know what
nwill hold, sonew int[n]withn = -1compiles and then throwsNegativeArraySizeException. A length of 0 is fine.
What a new element holds, by its type, with the lines of the run that show it:
| Element type | A new element holds |
|---|---|
int, and byte, short, long |
0: marks printed [0, 0, 0, 0, 0] |
double and float |
0.0: prices printed [0.0, 0.0, 0.0] |
boolean |
false: present printed four of them |
char |
the character with code 0, printed as the number 0 |
String and every other reference type |
null: names printed [null, null, null] |
Write the brackets after the type, int[] marks. Java also accepts the C style, int marks[], but the specification
recommends against mixing the two styles in one declaration, and int[] marks keeps the whole type in one place.
Checkpoint
Check: Array length and default values
The JVM checks every index you use
marks[0] is the first element and marks[marks.length - 1] the last; marks[2] = 90; replaces the third. The
index can be any expression of type int (a long index does not compile), and the JVM checks it every time the
access runs. The usual way to trip that check is a loop condition written with <=:
// The classic off-by-one mistake: i <= marks.length visits one index too many.
void main() {
int[] marks = {71, 64, 88, 92, 57};
int total = 0;
for (int i = 0; i <= marks.length; i++) { // the last valid index is marks.length - 1
total += marks[i];
IO.println("i = " + i + ": total " + total);
}
IO.println("total: " + total); // never reached
} Output (exit status 1)
i = 0: total 71 i = 1: total 135 i = 2: total 223 i = 3: total 315 i = 4: total 372
Printed as an error (standard error)
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 at OffByOne.main(OffByOne.java:6)
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java OffByOne.java
The loop printed five running totals and then stopped. Step through it to see why a sixth pass started at all, and where the check caught it:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Pass 1. i is 0, and 0 <= 5 is true. Index 0 is inside 0 to 4, so marks[0], 71, is added, and the run prints i = 0: total 71.
- Passes 2 to 4. 64, 88 and 92 follow at the indexes 1, 2 and 3, and the printed total grows to 135, 223 and 315.
- Pass 5, the last index. i is 4, the last valid index of a five-element array. marks[4] is 57, and the run prints i = 4: total 372.
- A sixth pass. i++ makes i 5, and 5 <= 5 is still true, so the loop starts a sixth pass over an array that has five elements.
- The index check. marks[5] asks for index 5 of an array of length 5. The JVM checks the index before it reads anything, finds it outside 0 to 4 and throws ArrayIndexOutOfBoundsException at line 6.
- Never printed. The exception ends main, so the line after the loop never runs. With i < marks.length the sixth test would be false, and the loop would leave after the fifth pass.
The message says what happened, Index 5 out of bounds for length 5, and the line under it, the top of the
stack trace: What an uncaught exception prints: its class and message, then the calls that were open when it was thrown, the most recent first, each with its file and line, and any Caused by sections. More in “IDEs, compiler errors and your first debug”, says where: line 6 of OffByOne.java. A five-element array has the indexes
0 to 4, and i <= marks.length lets i reach 5. The program stopped at that access, so the line after the loop never
ran, and nothing was read from outside the array. An index below 0 fails in the same way.
Testing i <= a.length
for (int i = 0; i <= marks.length; i++) runs one pass too many, the classic
off-by-one error: A loop that runs one pass too many or too few, usually from < written where <= was meant, or from a counter that starts at the wrong value. Checking the first and the last pass by hand catches it. More in “Loops: while, do-while, for and for-each”, and that pass throws. Write i < marks.length: the last index is
always length - 1.
Checkpoint
Check: Indexes and the bounds check
for-each gives you the values, and an indexed loop the positions too
Both loops from the lesson on loops work on arrays. The enhanced for loop: for (T x : items), also called for-each: it gives x each element of an array or an Iterable in turn, with no index, so it cannot step past the end, and x holds a copy of each element. More in “Loops: while, do-while, for and for-each”,
for (int m : marks), gives you each value in turn, with no index to get wrong. The indexed loop,
for (int i = 0; i < marks.length; i++), gives you the position as well. With arrays, the choice mostly comes down
to whether you need that position:
// Statistics over one test's marks (out of 100). A for-each loop where only the values matter,
// an indexed loop where the position matters too.
void main() {
String[] names = {"Asha", "Ravi", "Meera", "Kabir", "Divya", "Arjun"};
int[] marks = {71, 64, 88, 92, 57, 88}; // marks[i] belongs to names[i]
int total = 0;
for (int m : marks) { // every value, no index to get wrong
total += m;
}
double average = (double) total / marks.length;
int best = 0; // the INDEX of the highest mark so far
for (int i = 1; i < marks.length; i++) {
if (marks[i] > marks[best]) {
best = i;
}
}
int above = 0;
for (int m : marks) {
if (m > average) above++;
}
IO.println("students: " + marks.length + ", total: " + total);
IO.println("average: " + String.format("%.2f", average));
IO.println("highest: " + marks[best] + " by " + names[best] + " (index " + best + ")");
IO.println("above the average: " + above);
} Output
students: 6, total: 460 average: 76.67 highest: 92 by Kabir (index 3) above the average: 3
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Marks.java
The total and the count above the average need only the values, so for-each is the clearer loop for them. Finding
who scored highest needs a position: best holds the index of the best mark so far, and names[best] uses the
same index in the second array. Two arrays kept in step like this are called
parallel arrays: Two or more arrays kept in step by index, such as names[i] and marks[i] for one student. Sorting or changing one of them without the others breaks the link, which is why one array of records is usually safer.. They work, but the next lesson shows how sorting one of them
separates names from marks, and the cure, an array of records. Two details matter in the arithmetic:
(double) total / marks.length makes the division a decimal one (without the cast, 460 / 6 is 76), and starting with
best = 0 assumes the array has at least one element. An empty array would make marks[best] throw, so a method that
might receive one checks marks.length first.
| What the loop does | The loop to use |
|---|---|
| Reads every value: a total, a count, a print | for-each |
Needs the position: names[best] |
indexed |
Stores into the array: marks[i] = … |
indexed |
| Visits some positions only, or goes backwards | indexed |
The specification defines the for-each loop over an array as an indexed loop that assigns a[i] to the loop variable
at the start of every pass. So the variable is a copy of the element, and assigning to it never changes the array, as
the lesson on loops showed with numbers. In an array of objects, each element is a reference, and the copy refers to
the same object:
// The variable of a for-each loop is a copy of each element. In an array of objects, each
// element is a reference, so the loop can change the objects themselves, but it cannot put
// another object into the array.
void main() {
StringBuilder[] labels = {
new StringBuilder("A1"), new StringBuilder("A2"), new StringBuilder("A3"),
};
for (StringBuilder label : labels) {
label.append('*'); // changes the object this element refers to
}
IO.println("after append: " + Arrays.toString(labels));
for (StringBuilder label : labels) {
label = new StringBuilder("??"); // redirects only the loop's copy
}
IO.println("after assign: " + Arrays.toString(labels));
} Output
after append: [A1*, A2*, A3*] after assign: [A1*, A2*, A3*]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java ForEachObjects.java
Step through both loops, one reference at a time:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Three elements, three objects. labels is an array of three references, and each element refers to its own StringBuilder, holding A1, A2 and A3.
- The variable is a copy. The first pass of the first loop copies labels[0] into label. Both now hold the same reference, so both lead to the builder holding A1.
- append changes the object. label.append('') changes the object that label and labels[0] both refer to, so the array's first element now shows A1.
- Passes 2 and 3. The next passes copy labels[1] and labels[2] into label in turn and append to their objects. The first println prints after append: [A1*, A2*, A3*].
- Assigning to the copy. In the second loop, label again starts as a copy of labels[0]. label = new StringBuilder("??") makes the copy refer to a new object, and labels[0] still refers to A1*.
- The array is unchanged. Every pass redirects only its own copy, so the second println prints after assign: [A1*, A2*, A3*] again. Only labels[i] = … puts a different object into an element.
label.append('*') changed the object that the element and the copy both refer to, which the array then shows.
Assigning label = new StringBuilder("??") only redirected the copy, and the array kept its three builders. To put a
different object into an element, you need its index: labels[i] = ….
Checkpoint
Check: for-each and indexed loops
println shows an array’s class and hash code, and Arrays.toString its elements
Passing an array to IO.println or System.out.println does not print its elements:
// What println shows for an array, and what Arrays.toString shows.
void main() {
int[] marks = {71, 64, 88};
String[] cities = {"Pune", "Kochi"};
IO.println(marks); // its type and a hash code, not the elements
IO.println(cities);
IO.println(Arrays.toString(marks)); // the elements
IO.println(Arrays.toString(cities));
int[] missing = null;
IO.println(Arrays.toString(missing)); // "null", without an exception
} Output
[I@120d6fe6 [Ljava.lang.String;@4ba2ca36 [71, 64, 88] [Pune, Kochi] null
This output changes from run to run: the hexadecimal number after each @ is a hash code, which may differ from one run to the next
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Printing.java
An array inherits toString from Object, which returns the name of the object’s class, an @ and the object’s hash
code in hexadecimal. The five lines of the run, one by one:
| The run printed | What it is |
|---|---|
[I@ and a hash code |
[I is how the JVM names the class of int[] |
[Ljava.lang.String;@ and a hash code |
the name of the class of String[] |
[71, 64, 88] |
Arrays.toString(marks): the elements |
[Pune, Kochi] |
Arrays.toString(cities) |
null |
Arrays.toString(missing) for a null array |
The hash code identifies the object, not its contents, and the Object documentation warns that this text may change
from one run to the next. Arrays.toString is what you want: the elements in square brackets, separated by commas. It
also accepts null and returns the text null instead of throwing.
Checkpoint
Check: Printing an array
An array variable holds a reference, and an assignment shares it
An array is an object. The JVM allocates it on the
The memory where the JVM keeps the objects a program creates, shared by the whole program. A variable never holds an object itself, only a reference to one there.,
and an array variable holds a reference to it, just as a String variable holds a reference to a string. An
assignment between two array variables duplicates that reference and nothing else, which leaves you with two names for
one set of elements:
After int[] backup = marks; and int[] cloned = marks.clone();
Text description of the diagram
The diagram shows three array variables and the two array objects they refer to.
- marks refers to an array object of length 5 holding 71, 64, 88, 92 and 75.
- backup was declared as int[] backup = marks. That copied only the reference, so backup refers to the same array object. A change made through marks, such as marks[4] = 75, is seen through backup too, and marks == backup is true.
- cloned was declared as int[] cloned = marks.clone(). That created a second array object with its own copy of the five elements, so a later change to marks leaves cloned as it was, and cloned == marks is false.
// A "backup" that is not a backup: assigning an array variable copies the reference,
// not the elements.
void main() {
int[] marks = {71, 64, 88, 92, 57};
int[] backup = marks; // one array with two names
marks[4] = 75; // a re-evaluated paper
IO.println("marks " + Arrays.toString(marks));
IO.println("backup " + Arrays.toString(backup) + " <- changed as well");
IO.println("marks == backup: " + (marks == backup));
// Three ways to copy the elements into a new array.
int[] cloned = marks.clone();
int[] copied = Arrays.copyOf(marks, marks.length);
int[] longer = Arrays.copyOf(marks, 7); // extra elements are 0
int[] target = new int[5];
System.arraycopy(marks, 0, target, 0, marks.length);
marks[0] = 0; // change the original again
IO.println("marks " + Arrays.toString(marks));
IO.println("cloned " + Arrays.toString(cloned) + ", cloned == marks: " + (cloned == marks));
IO.println("copied " + Arrays.toString(copied));
IO.println("longer " + Arrays.toString(longer));
IO.println("target " + Arrays.toString(target));
} Output
marks [71, 64, 88, 92, 75] backup [71, 64, 88, 92, 75] <- changed as well marks == backup: true marks [0, 64, 88, 92, 75] cloned [71, 64, 88, 92, 75], cloned == marks: false copied [71, 64, 88, 92, 75] longer [71, 64, 88, 92, 75, 0, 0] target [71, 64, 88, 92, 75]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Aliasing.java
backup was meant to keep the old marks, yet it shows 75 straight away: int[] backup = marks; made no copy, and
marks == backup is true because == on arrays compares references. It asks whether two variables lead to the
same object, never whether two arrays hold the same values. Two names for one object are
alias: A second name for the same object: after int[] b = a; both variables refer to one array, so a change made through either is seen through the other, and a == b is true.. Step through the program and watch the arrows:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- One array. int[] marks = {71, 64, 88, 92, 57}; creates one array object, and marks holds a reference to it.
- A second name. int[] backup = marks; copies the reference and nothing else. backup is a second name for the same object, and no element was copied.
- A change seen through both. marks[4] = 75 writes into the only array there is, so marks and backup both print [71, 64, 88, 92, 75], and marks == backup is true.
- A real copy. marks.clone() creates a second array with its own copy of the five elements. Arrays.copyOf and System.arraycopy into a new int[5] make copies like it.
- Only the original changes. marks[0] = 0 changes the original, which backup still shares, while the clone keeps 71 at index 0, and cloned == marks is false.
A backup made with =
int[] backup = marks; makes a second name, not a backup: the run’s backup changed as soon as marks[4] did. Write
int[] backup = marks.clone();, or Arrays.copyOf(marks, marks.length), before you change anything.
Each of the three real copies is a new array, so each keeps its values when marks changes again:
marks.clone()returns a new array of the same type and length with the same elements. Every array has this publicclonemethod, and for anint[]it returns anint[], so no cast is needed.Arrays.copyOf(marks, n)returns a new array of lengthn. A shorter length cuts elements off; a longer one pads with the default value, here 0. It is the usual way to grow an array.System.arraycopy(src, srcPos, dest, destPos, length)copies into an array you already have, starting at any position on either side. When the two ranges overlap inside one array, it behaves as if it had copied through a temporary array, so the result is still right.
These copies are complete only for arrays of primitive type: One of the eight value types built into Java: byte, short, int, long, float, double, char and boolean. A variable of a primitive type holds the value itself and has no methods. More in “Variables and the eight primitive types” values. A copy of a String[],
or of any array of objects, holds the same references, so both arrays point to the same objects: a
shallow copy: A copy that copies the references an array or object holds but not the objects they refer to, so the copy and the original share those objects. clone() on an int[][] copies only the outer array of row references. More in “2D and jagged arrays for grids and matrices”, and the lesson on two-dimensional arrays shows where it bites. To compare
two arrays by their contents, use Arrays.equals(a, b), which the next lesson covers with the rest of the Arrays
class.
Key idea
b = a copies a reference, so both names reach one array; clone(), Arrays.copyOf and System.arraycopy into a
new array copy the elements.
Checkpoint
Check: References, aliases and copies
A method gets the reference by value, so it can change your elements
A method parameter of an array type receives a copy of the caller’s reference, never a copy of the array. Java passes every argument this way, pass-by-value: Passing a copy of each argument's value to the method's parameter, which is how Java passes every argument. For an object the value copied is a reference, so the method can change the object but never the caller's variable. More in “Methods, parameters, return and overloading”, and the value of an array variable is a reference:
// A method receives a copy of the reference. It can change the caller's elements,
// but pointing its own parameter at another array changes nothing for the caller.
void addGrace(int[] marks) {
for (int i = 0; i < marks.length; i++) {
marks[i] += 2; // writes into the caller's array
}
}
void replace(int[] marks) {
marks = new int[] {100, 100, 100}; // only the parameter points elsewhere now
}
int[] withGrace(int[] marks) {
int[] result = marks.clone(); // leave the caller's array as it is
for (int i = 0; i < result.length; i++) {
result[i] += 2;
}
return result;
}
void main() {
int[] marks = {71, 64, 88};
addGrace(marks);
IO.println("after addGrace: " + Arrays.toString(marks));
replace(marks);
IO.println("after replace: " + Arrays.toString(marks));
int[] graced = withGrace(marks);
IO.println("withGrace returned " + Arrays.toString(graced)
+ ", marks is still " + Arrays.toString(marks));
} Output
after addGrace: [73, 66, 90] after replace: [73, 66, 90] withGrace returned [75, 68, 92], marks is still [73, 66, 90]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java Methods.java
Step through the three calls and watch which arrows each method can move:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- main's array. In main, marks refers to an int[3] holding 71, 64 and 88.
- addGrace gets a copy. addGrace(marks) passes the value of main's variable, which is a reference. The parameter marks is a copy of it, so it refers to the same array.
- It writes through the copy. marks[i] += 2 writes into that one array, which becomes 73, 66 and 90, so main prints after addGrace: [73, 66, 90].
- replace gets a copy too. addGrace has returned and its parameter is gone. replace(marks) starts the same way: its parameter is another copy of main's reference.
- Only the copy moves. marks = new int[] {100, 100, 100}; points the parameter at a new array. main's variable still refers to 73, 66 and 90, which main prints again.
- withGrace copies first. withGrace clones the array before it changes anything, adds 2 only to the clone and returns [75, 68, 92], while main's marks are still [73, 66, 90].
addGrace changed the caller’s marks, because it wrote through the shared reference. replace changed nothing for
the caller: assigning a new array to marks inside the method only moved that method’s own copy of the reference.
withGrace is the safe pattern for a method that must leave its argument alone: copy first, change the copy, return
it. A method that changes an array it was given, without saying so, is one of the most common sources of bugs with
arrays, so when a method does change its argument, make that clear in its name and its documentation.
Checkpoint
Check: Arrays passed to methods
A String[] is also an Object[], so a wrong store fails while the program runs
Arrays are array covariance: The rule that an array of a subtype is also an array of its supertype, so a String[] may be assigned to an Object[]. The JVM then checks every store into an array of references while the program runs and throws ArrayStoreException for a value of the wrong type.: because a String can be assigned to an Object, a
String[] can be assigned to an Object[]. That suits methods that accept any array of objects, but it lets the
compiler accept a store that cannot work:
// Arrays are covariant: a String[] may be used wherever an Object[] is expected,
// so the JVM checks every store into an array of references while the program runs.
void main() {
String[] cities = {"Pune", "Kochi"};
Object[] things = cities; // compiles: a String[] is an Object[]
things[0] = "Jaipur"; // a String: allowed
IO.println(Arrays.toString(cities));
try {
things[1] = 42; // compiles, but the array holds Strings
} catch (ArrayStoreException e) {
IO.println("caught " + e);
}
IO.println(Arrays.toString(cities));
} Output
[Jaipur, Kochi] caught java.lang.ArrayStoreException: java.lang.Integer [Jaipur, Kochi]
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java StoreCheck.java
Step through the two stores, first as the compiler sees them, then as the JVM does:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A String[]. cities refers to an array object created as a String[], holding Pune and Kochi.
- The same object as Object[]. Object[] things = cities; compiles, because a String[] is also an Object[]. things is a second reference to the same object, which stays a String[] while the program runs.
- A String is stored. things[0] = "Jaipur" stores a String. The JVM checks the store, the value fits, and the next line prints [Jaipur, Kochi].
- 42 compiles too. things[1] = 42 also compiles: to the compiler, things is an Object[], and 42 becomes an Integer, which is an Object.
- The check refuses it. While the program runs, the JVM compares the value with the array's real element type. An Integer is not a String, so it throws ArrayStoreException: java.lang.Integer, and nothing is stored.
- The array kept its contents. The catch block prints the exception, and the last line prints [Jaipur, Kochi] again: the refused store never happened.
To the compiler, things is an Object[] and 42 is an Object, so things[1] = 42 compiles. The array object,
though, was created as a String[] and keeps that type while the program runs, and the JVM checks every store into an
array of references. The value is an Integer, not a String, so the store throws ArrayStoreException and the
array keeps its old contents. Generic collections close this gap at compile time instead: a List<String> is not a
List<Object>, so the same mistake with lists does not compile, which is one reason later modules prefer collections
in the parameters of methods.
Checkpoint
Check: Covariant arrays and the store check
Practise: running totals and rotation
Complete the two methods at the top of RunningTotals.java; the rest of the file is a set of checks that call them.
Both methods throw until you write them, so the first run lists eight failures and exits with status 1:
// Your turn. Write runningTotal and rotate, then run this file until every check passes:
// java RunningTotals.java
//
// runningTotal(a) returns a new array whose element i is a[0] + a[1] + ... + a[i]:
// {3, 1, 4} gives {3, 4, 8}.
// rotate(a, k) returns a new array in which every element moves k places to the right,
// and the ones that fall off the end come back at the front:
// rotate({1, 2, 3, 4, 5}, 2) gives {4, 5, 1, 2, 3}.
// A negative k moves the elements to the left.
// Neither method may change a, and each one creates exactly one new array.
int[] runningTotal(int[] a) {
throw new UnsupportedOperationException("not written yet");
}
int[] rotate(int[] a, int k) {
throw new UnsupportedOperationException("not written yet");
}
// ---- The checks. You do not need to change anything below this line. ----
int checks = 0;
int failed = 0;
void checkTotal(int[] a, int[] want) {
checks++;
String call = "runningTotal(" + Arrays.toString(a) + ")";
int[] before = a.clone();
try {
compare(call, a, before, runningTotal(a), want);
} catch (RuntimeException e) {
fail(call + " threw " + e);
}
}
void checkRotate(int[] a, int k, int[] want) {
checks++;
String call = "rotate(" + Arrays.toString(a) + ", " + k + ")";
int[] before = a.clone();
try {
compare(call, a, before, rotate(a, k), want);
} catch (RuntimeException e) {
fail(call + " threw " + e);
}
}
void compare(String call, int[] a, int[] before, int[] got, int[] want) {
if (!Arrays.equals(got, want)) {
fail(call + " returned " + Arrays.toString(got)
+ ", expected " + Arrays.toString(want));
} else if (got == a) {
fail(call + " returned its argument, not a new array");
} else if (!Arrays.equals(a, before)) {
fail(call + " changed its argument to " + Arrays.toString(a));
}
}
void fail(String message) {
failed++;
IO.println("FAIL " + message);
}
void main() {
checkTotal(new int[] {3, 1, 4}, new int[] {3, 4, 8});
checkTotal(new int[] {7}, new int[] {7});
checkTotal(new int[] {}, new int[] {});
checkRotate(new int[] {1, 2, 3, 4, 5}, 2, new int[] {4, 5, 1, 2, 3});
checkRotate(new int[] {1, 2, 3, 4, 5}, 0, new int[] {1, 2, 3, 4, 5});
checkRotate(new int[] {1, 2, 3, 4, 5}, 7, new int[] {4, 5, 1, 2, 3});
checkRotate(new int[] {1, 2, 3, 4, 5}, -1, new int[] {2, 3, 4, 5, 1});
checkRotate(new int[] {}, 3, new int[] {});
IO.println(failed + " of " + checks + " checks failed");
if (failed > 0) System.exit(1);
} Output (exit status 1)
FAIL runningTotal([3, 1, 4]) threw java.lang.UnsupportedOperationException: not written yet FAIL runningTotal([7]) threw java.lang.UnsupportedOperationException: not written yet FAIL runningTotal([]) threw java.lang.UnsupportedOperationException: not written yet FAIL rotate([1, 2, 3, 4, 5], 2) threw java.lang.UnsupportedOperationException: not written yet FAIL rotate([1, 2, 3, 4, 5], 0) threw java.lang.UnsupportedOperationException: not written yet FAIL rotate([1, 2, 3, 4, 5], 7) threw java.lang.UnsupportedOperationException: not written yet FAIL rotate([1, 2, 3, 4, 5], -1) threw java.lang.UnsupportedOperationException: not written yet FAIL rotate([], 3) threw java.lang.UnsupportedOperationException: not written yet 8 of 8 checks failed
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: java RunningTotals.java
You are done when java RunningTotals.java prints 0 of 8 checks failed. The checks also catch a method that
returns its argument instead of a new array, or that changes its argument: the two reference mistakes of this lesson.
One hint for rotate: element i moves to index (i + k) % n, but Java’s % keeps the sign of the number on its
left, so -1 % 5 is -1, not 4. Math.floorMod(-1, 5) gives 4. An empty array needs a case of its own, because
% 0 throws ArithmeticException.
Then try the practice below on the seven ideas of the lesson, from default values to the store check. It opens on the array rule you miss most, and every option spells out the array behaviour behind it:
Practice that adapts to you
Practise this lesson
19 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What does int[] b = a; do in Java, and how do you get an independent copy of an int array?
It copies the reference, not the array. After the assignment, a and b refer to the same array object, so
b[0] = 9 is visible through a, and a == b is true. To get an independent copy, create a new array with the
same elements: a.clone(), Arrays.copyOf(a, a.length), which can also make the copy longer or shorter, or
System.arraycopy into an array you created yourself. For an array of primitives such as int[], each of these is
a complete copy. For an array of objects, each copies only the references, so the copy and the original share the
objects themselves. A good answer also says how to check the result: a == copy is false, while
Arrays.equals(a, copy) is true.
Interviewers often follow this answer with copies of arrays of objects. Work out what each snippet does before you look at the reasons:
Checkpoint
Interview round: two follow-up questions
This lesson is also the starting point of two longer questions in the module bank: a helper that sorts the array it
was given, and a library method that throws ArrayStoreException for one caller only.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A helper median(int[] scores) sorts its argument to find the middle value. After a release, a leaderboard that calls it shows the scores in ascending order instead of the order players finished. Explain the bug, fix it, and say what your fix costs for 10 million scores.
-
Mid to senior
A library method fillDefaults(Object[] slots, Object value) passes its tests, but in production one caller gets ArrayStoreException. Why can the compiler not catch this, how do you find the caller, and how would you change the API so that the mistake no longer compiles?
Key takeaways
- An array has a fixed length, set by
new int[n]or counted from an initialiser such as{71, 64, 88}, and every new element starts at its type’s default: 0,0.0,false, the character with code 0, ornull. - Indexes run from 0 to
length - 1, and the JVM checks every access: a loop withi <= a.lengththrowsArrayIndexOutOfBoundsExceptionon its extra pass, and the message names the index and the length. - for-each reads every value with no index to get wrong; its variable is a copy of each element, so it can change the objects an array refers to but never replace an element, which takes an indexed loop.
- Printing an array shows its class name and a hash code, such as
[I@and hexadecimal digits;Arrays.toStringshows the elements. - An array variable holds a reference:
b = ashares one array,==compares references,clone(),Arrays.copyOfandSystem.arraycopycopy the elements, and a method can change the elements of an array it receives, so copy first when it must not. - Because a
String[]is also anObject[], storing a value of the wrong type compiles and then throwsArrayStoreException.
Terms in this lesson
- alias
- A second name for the same object: after int[] b = a; both variables refer to one array, so a change made through either is seen through the other, and a == b is true.
- array covariance
- The rule that an array of a subtype is also an array of its supertype, so a String[] may be assigned to an Object[]. The JVM then checks every store into an array of references while the program runs and throws ArrayStoreException for a value of the wrong type.
- default value
- The value every element of a new array starts with, before anything is stored in it: 0 for the integer types, 0.0 for float and double, false for boolean, the character with code 0 for char, and null for every reference type.
- parallel arrays
- Two or more arrays kept in step by index, such as names[i] and marks[i] for one student. Sorting or changing one of them without the others breaks the link, which is why one array of records is usually safer.
References
- The Java Language Specification, Java SE 25 Edition: Chapter 10, Arrays (Oracle)
- The Java Language Specification, Java SE 25 Edition: 4.12.5 Initial Values of Variables (Oracle)
- The Java Language Specification, Java SE 25 Edition: 14.14.2 The enhanced for statement (Oracle)
- The Java Language Specification, Java SE 25 Edition: 15.10 Array Creation and Access Expressions (Oracle)
- The Java Language Specification, Java SE 25 Edition: 15.12.4.2 Evaluate Arguments (Oracle)
- The Java Language Specification, Java SE 25 Edition: 15.17.3 Remainder Operator % (Oracle)
- The Java Virtual Machine Specification, Java SE 25 Edition: 2.5.3 Heap (Oracle)
- Java SE 25 API: java.util.Arrays (Oracle)
- Java SE 25 API: System.arraycopy (Oracle)
- Java SE 25 API: Object.toString (Oracle)
- Java SE 25 API: Class.getName (Oracle)
- Java SE 25 API: ArrayIndexOutOfBoundsException (Oracle)
- Java SE 25 API: ArrayStoreException (Oracle)
- Java SE 25 API: Math.floorMod (Oracle)
- JEP 512: Compact Source Files and Instance Main Methods (OpenJDK)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress