Java Arrays and Multidimensional Arrays
Store related values under one name, access only positions that exist, and summarize rows without confusing empty data, missing data, or shared storage.
Before you start: review indexed and enhanced for loops and values and reference types. All source examples run with Java 21. Predict the output, run the example, and edit the array values or structure to test your explanation.
What you will be able to do
- Create arrays with initial values or a fixed number of default-initialized elements.
- Use zero-based indexes and distinguish length from the last valid position.
- Traverse and update elements deliberately with indexed or enhanced for loops.
- Distinguish an empty array, a null reference, and an uninitialized local variable.
- Explain shared references, independent one-dimensional copies, and shallow nested copies.
- Summarize rectangular and ragged arrays without inventing values for missing data.
An array groups positions under one name
Suppose a learner has three scores. Separate variables such as scoreOne, scoreTwo, and scoreThree make it awkward to repeat the same operation for every score. An int[] gives those values one array name and an indexed position for each element. Every element in this example stores an int.
The initializer {64, 78, 91} creates a three-element array. Position 0 contains 64, position 1 contains 78, and position 2 contains 91. Read scores[1] as the element at index 1, which is the second element, not the first. Using a zero-based position consistently is more important than memorizing one example.
An array object's length is fixed after creation, but its elements can change. Assigning scores[1] = 82 replaces that stored value without creating a fourth position or moving the other entries. If an application needs a growing collection, ArrayList will be a later topic; an array does not gain an element simply by assigning beyond its bounds.
Main.java · Read and update a position
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Length: 3
First score: 64
Updated second score: 82
Last score: 91
Walk through the second-score update
- The three positions initially contain 64, 78, and 91.
- scores[1] selects the second position.
- Assigning 82 replaces 78 at that position.
- The length is still 3, and the last element is still 91.
Change the first position instead. Which printed lines should change, and which should remain the same?
Creating an array is different from declaring its variable
Use an initializer when the initial values are known. Use new int[3] when you need three positions whose values will be assigned later. Writing only int[] attempts declares a local reference variable; it does not create an array. Java will reject reading that local variable before it has been assigned.
A newly created array initializes its elements according to their type. int elements start at 0, boolean elements at false, and reference elements such as String at null. These element defaults do not give an unassigned local variable a usable array automatically. A default zero is also not proof that a learner actually submitted a zero score.
new String[2] creates the array, not two text values. Calling a method on an unassigned null element can fail even when the array itself exists. Assign meaningful data before using it. For a later assignment to an existing variable, write names = new String[] {"Mina", "Leo"}; rather than a bare brace initializer on its own.
Main.java · New elements have type-specific defaults
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Default attempts: 0
Default completed: false
Default name: null
Assigned name: Mina
Third attempt count: 4
length is a count; the last valid index is length minus one
For an array of length three, the valid indexes are 0, 1, and 2. The expression scores[scores.length - 1] reads the last element only when at least one element exists. There is no element at index scores.length. A negative index is invalid as well.
An indexed traversal normally uses index = 0 with index < scores.length. Using <= eventually attempts one access beyond the end and throws ArrayIndexOutOfBoundsException. Java checks these accesses at runtime; a program may compile successfully and still fail for an empty array or an incorrect boundary.
The length member has no parentheses: scores.length. This differs from the String method name.length(). An array's length cannot be assigned a new value. To work with a different length, create another array and, when needed, copy the existing values. Also validate a dynamic requested size before creating an array; a negative size is not allowed.
Read values with enhanced for; write positions deliberately
Enhanced for is convenient for totals and checks that only need the current value. With a primitive int array, the local loop variable receives a copy of each element's value. Adding five to that local variable does not write the new number back into its source position.
The first traversal in this program therefore leaves the array unchanged. The indexed traversal explicitly assigns to scores[index], which changes the stored element. Both loops visit the same starting values, but the left-hand side of the assignment determines whether array storage is actually updated.
Keep the operation's intent visible. Use enhanced for when reading is enough; use an index when a position, replacement, reverse direction, or stride matters. Arrays.toString formats the elements for inspection. Printing an array reference directly is not a useful way to check whether its values changed.
Main.java · Change stored values with an indexed loop
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
After local changes: [40, 65, 90]
After element changes: [45, 70, 95]
A summary needs an empty case and a valid starting value
The readings example deliberately uses negative values. If highest started at zero, the result would incorrectly claim a highest reading of 0 even though 0 does not occur in the data. After checking that the array is nonempty, seed highest from the first actual element instead.
The loop adds every reading to total and updates highest only when it encounters a larger value. A long total gives more room than an int sum. The cast before division makes the average floating-point rather than truncated integer arithmetic. Formatting rounds the displayed result; it does not change the underlying calculation.
An empty array has no first value and no arithmetic mean to report. Handle that case before reading index 0 or dividing by length. The early return prints a missing-result message instead of inventing a maximum of zero or trying to compute an average with no observations.
Main.java · Summarize without a false maximum
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Highest: -3
Average: -7.33
Explain the negative-data result
The highest candidate starts at -8, becomes -3, and stays -3 when -11 is visited. The total is -22, divided by three readings using floating-point arithmetic. The displayed average is -7.33. Try a single reading, then an empty initializer: the latter must print the no-readings message without accessing index 0.
Empty, missing, and unassigned are three different states
An empty array is a real array with length 0. It can be traversed safely because the body runs zero times, but it has no readable first or last element. A null reference means there is no array object to inspect, so even reading its length would fail without a guard.
The combined guard uses missing == null || missing.length == 0. Because || short-circuits, Java does not evaluate the length access when the null comparison is true. Reversing those checks would attempt the unsafe access first. A local variable that has never been assigned is different again: Java reports a compile-time definite-assignment error when it is read.
Whether empty and missing mean the same thing is an application policy. A recorded session containing zero scores may be a valid empty collection; a missing session may mean it was never supplied. The lab keeps these row states distinct rather than silently converting a missing row into an observed zero score.
Main.java · Distinguish empty from missing
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Empty length: 0
No readable first element.
Empty traversal finished.
Assignment shares an array; a copy creates different storage
An array variable holds a reference to an array object. int[] alias = original copies that reference, not the elements. Both variables then identify the same storage, so changing alias[0] also changes the value observed through original[0]. The array did not synchronize two independent copies; there was only one array to begin with.
Arrays.copyOf(original, original.length) creates another one-dimensional int array and copies the values. Changes to that snapshot do not change the original primitive elements. Reassigning a variable to a different array also does not redirect aliases that still refer to the old object.
Copying with a larger requested length creates additional default-initialized positions; copying with a smaller length keeps only the leading values that fit. It does not resize the old object. Similarly, final int[] prevents reassignment of that reference variable, but does not make the referenced elements immutable.
Main.java · Assignment shares; copying separates
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Original: [99, 20]
Alias: [99, 20]
Snapshot: [10, 77]
Original and alias share: true
Use content-aware tools to print and compare
For arrays, == asks whether two references identify the same array object. It does not compare the sequence of values. Two separately created arrays can therefore print Same reference: false even when both contain 10 followed by 20.
Arrays.equals compares the contents of these one-dimensional int arrays in order. Arrays.toString gives a readable representation such as [10, 20]. Both helpers belong to java.util.Arrays, which this example imports. Neither operation changes the input arrays.
For nested arrays, Arrays.deepToString produces readable row contents and Arrays.deepEquals compares nested contents. Ordinary Arrays.equals on an int[][] compares its row references rather than recursively comparing the numbers. Content equality is also separate from whether later mutations through shared rows can affect both structures.
Main.java · Compare values rather than references
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Same reference: false
Same contents: true
Values: [10, 20]
A two-dimensional array is an array of row references
Read int[][] as an array whose elements are themselves int arrays. In minutes[row][column], the first index selects a row array, then the second index selects an element within that row. minutes.length counts rows; minutes[row].length counts elements in one selected row.
The rectangular example has two rows of three entries. minutes[1][2] selects the second row's third entry, 35. The outer loop selects each row, and the inner loop adds that row's values. total is reset for each row because each printed result is a separate row total.
new int[2][3] also creates two non-null rows with three zero-initialized int elements each. By contrast, new int[2][] creates two row slots whose values are initially null; it does not create their inner arrays. Establish the row before reading its length or its elements.
Main.java · Traverse a row and column
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rows: 2
Second row, third value: 35
Row 1 total: 60
Row 2 total: 75
Rows may have different lengths, be empty, or be missing
Java does not require every row to have the same length. The sessions example has a two-entry row, an empty row, a null row, and a one-entry row. This is often called a ragged or jagged array. Each existing row has its own independent length.
Check whether a row is null before accessing it. For an existing row, traverse that row's values or use its own length as the inner loop boundary. Reusing sessions[0].length for every row can fail on shorter rows and can ignore extra entries on longer rows.
The output distinguishes an empty row with entries=0 from a missing row. An empty outer array has no rows at all, so the outer loop runs zero times. Do not use row zero to determine a universal width unless your own data contract has already established that a nonempty rectangular grid exists.
Main.java · Respect each row's own length
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Row 1: entries=2, total=50
Row 2: entries=0, total=0
Row 3: missing
Row 4: entries=1, total=15
Why not use the first row as a template?
The first row has two entries, but the last row has only one. Applying the first row's length to the last row would attempt a nonexistent second element. The empty row has no element at all, and the missing row has no array whose length could be read.
Copying the outer array does not copy its rows
Arrays.copyOf on int[][] creates another outer array, but copies the row references into it. The two outer arrays are different objects, while corresponding existing rows are initially shared. This is a shallow copy, not a full independent snapshot of all the nested integer storage.
Changing outerCopy[0][0] edits a shared row, so original also shows 99. Assigning a new array to outerCopy[1] instead replaces only that outer array's second row reference. original still refers to its old second row containing 30. Distinguish editing an element of a shared row from replacing a row reference.
For independent primitive rows, create a new outer array and copy each non-null int row separately. Preserve null rows deliberately if missing data matters. Do not assume Arrays.deepToString or deepEquals performs that copy: they format and compare, not duplicate storage. More deeply nested or object-containing structures require their own copying policy.
Main.java · A nested copy still shares its rows
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Original: [[99, 20], [30]]
Outer copy: [[99, 20], [77]]
Same outer array: false
Same first row: true
Predict the two different nested changes
The first mutation follows a shared row reference, so both structures show 99. The second assignment replaces one row slot only in outerCopy, so original keeps its row containing 30. A new outer array is not enough to isolate every nested mutation.
Repair exercise: stop at the final valid position
This program should print every score and its total. It compiles but crashes after the last valid score. Repair the boundary without hardcoding the array length, then test an empty array too.
Main.java · Repair an index boundary
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Reveal the explained repair
The <= condition allows index 3 when length is 3, but the last valid position is 2. Change it to < so only actual elements are accessed. With an empty initializer {}, the first check is false, no score prints, and total remains 0. Removing the final printed score or catching the exception would not fix the incorrect traversal rule.
Main.java · Repaired array traversal
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Score: 10
Score: 20
Score: 30
Total: 60
Independent lab: report scores across unequal sessions
Build a score report for a group of sessions. Each row contains that session's submitted scores. Accept only scores from 0 through 100, preserve the distinction between empty and missing rows, and calculate an overall average weighted by actual valid scores. Edit the array in the source to test data changes; this lab does not read standard input.
- Process rows in order without changing the supplied arrays. A null row prints Row N: missing and is not traversed.
- Reject integer scores below 0 or above 100. Count each rejected element once, and do not add it to any sum, average, or extreme.
- For each non-null row with valid scores, print Row N: count=C, average=A with two decimal places using Locale.ROOT. For a row with no valid scores, print Row N: no valid scores.
- Track a global valid count, rejected count, long total, and lowest/highest valid score. Seed extremes from the first actual valid score, not from an assumed zero.
- After all rows print Valid scores: and Rejected scores:. If any valid score exists, print Overall average:, Lowest score:, and Highest score:. Otherwise print Overall: no valid scores. and omit extrema.
- Calculate the overall average from the total and count of all valid scores, not the average of row averages. Guard empty data before division or reading an element.
The starter includes five rows: three submitted scores, an empty row, two scores, a missing row, and two more scores. Two submitted values are invalid. Expected output:
Row 1: count=2, average=80.00
Row 2: no valid scores
Row 3: count=2, average=82.00
Row 4: missing
Row 5: count=1, average=80.00
Valid scores: 5
Rejected scores: 2
Overall average: 80.80
Lowest score: 64
Highest score: 100
Main.java · Build your session score report
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Hints before the solution
Keep global totals outside the outer loop, but reset each row's total and count inside it. Guard a null row first. Reject a bad score before updating either level of totals. On the first valid score, initialize both extremes from that score. Only divide when the relevant count is positive.
Use System.out.printf with Locale.ROOT and %.2f for two decimal places. Cast the total to double before dividing. This controls display formatting while preserving the intended arithmetic.
Reveal the explained lab solution
Main.java · Session score-report solution
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Row totals reset for each existing row, while global totals survive the outer iterations. Missing rows have no elements to reject; empty rows have no observations to average. The zero initializations for lowest and highest are only placeholders: the first valid score overwrites both before either is used as a result. The five accepted scores sum to 404, so the overall average is 404 / 5.0 = 80.80. Averaging the three nonempty row averages would weight a one-score row the same as a two-score row and give a different result. Rejected elements never affect either level of calculation.
Test changes and boundaries
Edit the sessions initializer in the source for each test; no standard input is required. Reset restores the original source. Check the accepted count, rejected count, and average together so a plausible-looking average cannot hide dropped or invented observations.
- Use the supplied data: five accepted scores, two rejected scores, and an overall average of 80.80. Confirm the source array values remain unchanged.
- Replace the outer initializer with {}. Expect zero valid/rejected counts and Overall: no valid scores., with no row or extrema lines.
- Try {null, {}, {-1, 101}}. Distinguish the missing row from two rows with no valid scores; reject two elements and avoid all division by zero.
- Try {{0, 100}} for the inclusive domain boundaries. Expect an average of 50.00, lowest 0, and highest 100. Try {{75}} to verify both extrema are seeded from the actual score.
- Try {{0, 100}, {100}}. The overall average must be 66.67, not 75.00. This exposes incorrect averaging of differently sized rows.
Common mistakes and how to diagnose them
ArrayIndexOutOfBoundsException
Check the exact index and the selected array's length. A length of n permits 0 through n - 1. Guard first/last reads for empty arrays, and use each existing row's own length in nested traversal.
NullPointerException despite a non-null outer array
A row or reference element may still be null. new int[2][] creates an outer array with missing rows. Check the reference you are about to use, not just the outer container.
The local array variable might not have been initialized
Declaring int[] scores creates a variable, not an array. Assign an array before reading it. Element defaults apply after array creation and do not initialize every local reference automatically.
Editing a copy unexpectedly changes the original
Assignment aliases the same object, and copying an int[][] outer array still shares row objects. Choose a copy depth that matches the intended independence, and distinguish element mutation from replacing a reference.
The average is truncated or weights rows incorrectly
Cast before division to preserve fractions. Divide the grand total by the number of valid elements; do not average row means when their counts differ. Check the empty case before computing a mean.
The maximum is zero although every reading is negative
Zero was likely used as a false starting observation. After a nonempty guard, seed from an actual element. When validation can reject elements, seed from the first accepted value and handle the no-valid-values case separately.
Frequently asked questions
Can an array grow after creation?
The array object's length is fixed. You can create another array of a different length and copy values into it, then reassign a non-final variable. A later collections lesson covers ArrayList for dynamically sized groups.
Is an empty array the same as null?
No. An empty array exists and has length zero. null refers to no array object, so accessing its length or elements fails. An unassigned local variable is a separate compile-time issue.
Does final make array elements read-only?
No. final prevents assigning a different array reference to that variable. Existing element values can still be changed unless your own program prevents those mutations.
Must every row have the same number of columns?
No. Java multidimensional arrays contain row references, so rows can have different lengths, be empty, or be null. Traverse each existing row with its own boundary.
Is Arrays.copyOf a deep copy?
For a one-dimensional int array it copies primitive values into independent storage. For an int[][] it creates an independent outer array but copies row references. Copy each non-null row as well when independent primitive row storage is required.
Identify the storage, its boundaries, and its meaning
Array length counts positions; it is not the final index. Element mutation differs from reference reassignment. Empty arrays differ from null references, and an independent outer copy may still share rows. Traverse each existing row according to its own length and keep missing results explicit.
Before moving on, explain why the lab's overall average is not an average of row averages, why rejected values never seed an extreme, and why a missing row cannot be inspected like an empty row. Next: methods and return values, where you will start dividing programs into reusable operations.
Can you explain which values and rows actually exist?
Answer all six questions and read the explanations. Results are temporary on this page and do not save course progress.
References and related learning
The scenarios, explanations, examples, and exercises are original SovranCode material. Official references support the Java 21 language and library rules.
Review traversal and skip rules · Review references and primitive values · Review calculations and formatting · Java course roadmap.
Last updated: