Arrays in Java — practice with a shop, a matrix and the class grades (and how not to ruin the original)
A while ago I wrote a method to reverse an array. I tested it, it printed the perfect result, and, delighted, I also printed the original array to compare… and it was reversed too. My “before” array had vanished. It took me a while to understand: my method hadn’t worked on a copy, it had reversed the very same array I passed in. That slip taught me more about arrays than any definition, and it shows up several times in this practice.
If you’ve already read the arrays theory, this article is for applying it with three complete programs: a shop inventory, matrix operations and statistics for a set of grades. All of them are medium difficulty and are meant for you to copy, run and deliberately break to see what happens. Each one comes with commented code, its output and the doubts that usually come up.
Table of Contents
Before you start
To follow along, you should be comfortable with:
- How an array is declared and created (
{...}andnew) and thatlengthhas no parentheses. - That an array is passed to a method by reference: the method can modify the original.
- How to loop with
forand with for-each, and how to loop over a matrix with two loops.
If any of these sounds like Greek, go back to the theory for a moment. You’ll be glad you did in the second program.
Arrays in Java practice — Program 1: the shop (parallel arrays)
We’re going to keep the inventory of a stationery shop. We have five products, each with its name, its price and its units in stock. The program has to list the inventory, sell products (checking that they exist and that there’s stock), calculate the total value and flag what’s sold out.
The idea: parallel arrays
We use three arrays of the same size in which position i always describes the same product: products[i], prices[i] and stock[i]. It’s a very common technique before classes have been introduced (later this would be solved with objects, but it’s worth mastering this first).
Before programming, think about what you need to know for each operation. To sell “Backpack”, the first thing is to know which position it’s in. That’s why we start with a find method that returns that position, and all the others reuse it.
The code
public class Shop {
// Returns the position of the product, or -1 if it doesn't exist
static int find(String[] products, String name) {
for (int i = 0; i < products.length; i++) {
if (products[i].equals(name)) { // equals, not ==: these are Strings
return i;
}
}
return -1; // -1 is never a valid position
}
// Tries to sell. Returns true if the sale went through.
static boolean sell(String[] products, int[] stock, String name, int units) {
int pos = find(products, name);
if (pos == -1) {
System.out.println(" Product not found: " + name);
return false;
}
if (stock[pos] < units) {
System.out.println(" Not enough stock of " + name + ": only " + stock[pos] + " left");
return false;
}
stock[pos] -= units; // modifies the original array: no need to return it
return true;
}
// Adds up price * units for every product
static double inventoryValue(double[] prices, int[] stock) {
double total = 0;
for (int i = 0; i < prices.length; i++) {
total += prices[i] * stock[i];
}
return total;
}
static void list(String[] products, double[] prices, int[] stock) {
for (int i = 0; i < products.length; i++) {
System.out.printf(" %-10s %6.2f € %3d units%n", products[i], prices[i], stock[i]);
}
}
static void showSoldOut(String[] products, int[] stock) {
boolean anySoldOut = false;
for (int i = 0; i < stock.length; i++) {
if (stock[i] == 0) {
System.out.println(" Sold out: " + products[i]);
anySoldOut = true;
}
}
if (!anySoldOut) {
System.out.println(" Nothing is sold out");
}
}
public static void main(String[] args) {
// Parallel arrays: position i describes the same product in all three
String[] products = {"Notebook", "Pen", "Backpack", "Ruler", "Folder"};
double[] prices = {2.50, 0.90, 24.00, 1.20, 3.75};
int[] stock = {10, 50, 3, 0, 8};
System.out.println("Starting inventory:");
list(products, prices, stock);
System.out.printf("Total value: %.2f €%n%n", inventoryValue(prices, stock));
System.out.println("Sales:");
sell(products, stock, "Backpack", 2);
sell(products, stock, "Backpack", 2);
sell(products, stock, "Compass", 1);
sell(products, stock, "Notebook", 10);
System.out.println("\nFinal inventory:");
list(products, prices, stock);
System.out.printf("Total value: %.2f €%n", inventoryValue(prices, stock));
showSoldOut(products, stock);
}
}
Output
Starting inventory: Notebook 2.50 € 10 units Pen 0.90 € 50 units Backpack 24.00 € 3 units Ruler 1.20 € 0 units Folder 3.75 € 8 units Total value: 172.00 € Sales: Not enough stock of Backpack: only 1 left Product not found: Compass Final inventory: Notebook 2.50 € 0 units Pen 0.90 € 50 units Backpack 24.00 € 1 units Ruler 1.20 € 0 units Folder 3.75 € 8 units Total value: 99.00 € Sold out: Notebook Sold out: Ruler
Look at the sales: the first Backpack sale (2 units) goes through and prints nothing; the second fails because only 1 was left; “Compass” doesn’t exist; and the Notebook sale sells all 10 units and leaves it sold out.
printf uses the format of your language: with your system in English you’ll see 2.50; with it in Spanish, 2,50. It’s not a bug, it’s the decimal separator of your settings.
Questions you might be asking
Why equals and not == in find? Because == compares addresses, not contents, just like it did with arrays. With String you have to compare with equals.
Why does it return -1 if it doesn’t find it? Because -1 is never a valid index, so whoever calls the method can tell “it’s at position 0” from “it isn’t there”. It’s a very common convention in Java (binarySearch does something similar).
Why doesn’t sell return the updated stock? There’s no need: it receives the address of the stock array, so stock[pos] -= units changes the original array. This is exactly what we saw in “Arrays and methods”.
What if two products have the same name? find would always return the first one. In a real shop that would be a problem and unique codes would be used; here we assume names aren’t repeated.
The typical mistake in this program: having the three arrays with different sizes (for example, one price too many or too few). Nothing warns you when compiling; as soon as you loop with prices.length and access stock[i], an ArrayIndexOutOfBoundsException will pop up. If you use parallel arrays, always check that they have the same length.
Try it yourself
- Add a method
restock(products, stock, name, units)that adds units to the stock. - Add
mostExpensive(products, prices)that returns the name of the most expensive one (hint: find the position of the maximum, as in the theory).
Arrays in Java practice — Program 2: matrix operations
Now, tables. We’re going to work with an integer matrix (imagine the sales of three shops over four weeks) and calculate the sum by rows, the sum by columns, the diagonal sum and the transpose. Along the way, a method that checks whether a matrix is rectangular, because in Java a “table” doesn’t have to be.
The idea
A matrix is an array of arrays: m.length is the number of rows and m[i].length is the number of columns in row i. The row sum stores its result in an array with one position per row; the column sum, in one with one position per column. Understanding which index rules in each case is the key to the program.
The code
import java.util.Arrays;
public class Matrix {
// Checks that every row has the same length
static boolean isRectangular(int[][] m) {
for (int i = 1; i < m.length; i++) {
if (m[i].length != m[0].length) {
return false;
}
}
return true;
}
static int[] rowSums(int[][] m) {
int[] sums = new int[m.length]; // one sum per row
for (int r = 0; r < m.length; r++) {
for (int c = 0; c < m[r].length; c++) {
sums[r] += m[r];
}
}
return sums;
}
static int[] columnSums(int[][] m) {
int[] sums = new int[m[0].length]; // one sum per column: we use the length of row 0
for (int r = 0; r < m.length; r++) {
for (int c = 0; c < m[r].length; c++) {
sums += m[r]; // the sum goes to the COLUMN's position
}
}
return sums;
}
// Only makes sense for square matrices
static int diagonalSum(int[][] m) {
int sum = 0;
for (int i = 0; i < m.length; i++) {
sum += m[i][i];
}
return sum;
}
// Rows and columns swapped: the original is left untouched
static int[][] transpose(int[][] m) {
int[][] t = new int[m[0].length][m.length]; // reversed! columns x rows
for (int r = 0; r < m.length; r++) {
for (int c = 0; c < m[r].length; c++) {
t[r] = m[r];
}
}
return t;
}
static void print(int[][] m) {
for (int[] row : m) {
for (int value : row) {
System.out.printf("%4d", value);
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] sales = {
{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9, 10, 11, 12}
};
System.out.println("3x4 matrix:");
print(sales);
System.out.println("Rectangular? " + isRectangular(sales));
System.out.println("Row sums: " + Arrays.toString(rowSums(sales)));
System.out.println("Column sums: " + Arrays.toString(columnSums(sales)));
System.out.println("\nTranspose (4x3):");
print(transpose(sales));
int[][] square = {
{2, 0, 1},
{3, 5, 4},
{1, 6, 9}
};
System.out.println("\nDiagonal sum of the square matrix: " + diagonalSum(square));
int[][] irregular = {
{1, 2, 3},
{4, 5},
{6}
};
System.out.println("Is the irregular one rectangular? " + isRectangular(irregular));
}
}
Output
3x4 matrix: 1 2 3 4 5 6 7 8 9 10 11 12 Rectangular? true Row sums: [10, 26, 42] Column sums: [15, 18, 21, 24] Transpose (4x3): 1 5 9 2 6 10 3 7 11 4 8 12 Diagonal sum of the square matrix: 16 Is the irregular one rectangular? false
Check the first row by hand: 1 + 2 + 3 + 4 = 10 ✔. And the first column: 1 + 5 + 9 = 15 ✔. And the diagonal of the square one: 2 + 5 + 9 = 16 ✔.
Questions you might be asking
Why does the result go to sums[r] in rowSums and to sums in columnSums? It’s the key to the whole program. Each sum must accumulate in the position that corresponds to what you’re adding up: if you add by rows, the row r rules; if you add by columns, the column c rules. If you mix up the index, there’s no error: the program just gives wrong numbers.
Why does columnSums create the array with m[0].length? Because it needs to know how many columns there are before it starts, and the way to know is to look at the length of a row. That’s why it works well with rectangular matrices, and why isRectangular exists: with the irregular matrix, that method would fail or give meaningless results.
Why is the array in transpose created with [m[0].length][m.length]? Because the transpose swaps rows and columns: a matrix of 3 rows × 4 columns becomes one of 4 rows × 3 columns. And that’s why the assignment is t[r] = m[r]: what was in row r, column c, moves to row c, column r.
Why does transpose create a new matrix instead of modifying the original? Because with non-square matrices you can’t transpose “in place”: the size changes (3×4 becomes 4×3), and an array can’t change size. Besides, this way the original stays intact, which is exactly what my anecdote was teaching.
Trying diagonalSum with the 3×4 matrix. Since m[i][i] with i = 3 doesn’t exist (row 3 doesn’t exist in a 3-row matrix), it either runs out of columns or gives a result that means nothing. The diagonal only makes sense in square matrices: if you want to do it properly, first check that m.length == m[0].length.
When you write two nested loops, name the variables r (row) and c (column), or row and col, instead of i and j. It seems like a detail, but it’s the fastest way not to swap them without noticing.
Try it yourself
- Write
totalSum(m)that returns the sum of all the elements. - Write
bestRow(m)that returns the index of the row with the highest sum (in our example, row 2).
Arrays in Java practice — Program 1: statistics for a set of grades
The last program is the most “exam-like”: given an array with a group’s grades (from 0 to 10), calculate the mean, the median, the mode, how many students are above the mean, and draw a histogram with asterisks.
The idea
There are two interesting things here:
- The median needs the array sorted, but we don’t want to sort the original (remember my anecdote). Solution: work on a copy.
- The mode is calculated with a frequency array. Since grades go from 0 to 10, we can create an array of 11 positions where position
ncounts how many times gradenappears. That is, the grade is used as the index. It’s a very elegant trick and it also works for the histogram.
The code
import java.util.Arrays;
public class Statistics {
static double mean(int[] data) {
int sum = 0;
for (int n : data) {
sum += n;
}
return (double) sum / data.length;
}
// Works on a COPY: sorting the original would be an unwanted side effect
static double median(int[] data) {
int[] sorted = Arrays.copyOf(data, data.length);
Arrays.sort(sorted);
int n = sorted.length;
if (n % 2 == 1) {
return sorted[n / 2]; // odd: the middle one
}
return (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0; // even: average of the two middle ones
}
// Grades from 0 to 10: counts[n] holds how many times grade n appears
static int[] frequencies(int[] grades) {
int[] counts = new int[11];
for (int grade : grades) {
counts[grade]++; // the grade IS the index
}
return counts;
}
// Returns the most repeated grade (on a tie, the lowest one)
static int mode(int[] grades) {
int[] counts = frequencies(grades);
int best = 0;
for (int grade = 1; grade < counts.length; grade++) {
if (counts[grade] > counts[best]) { // strict >: on a tie the lowest wins
best = grade;
}
}
return best;
}
static int countAbove(int[] data, double threshold) {
int count = 0;
for (int n : data) {
if (n > threshold) {
count++;
}
}
return count;
}
static void histogram(int[] grades) {
int[] counts = frequencies(grades);
for (int grade = 0; grade < counts.length; grade++) {
System.out.printf("%2d | ", grade);
for (int i = 0; i < counts[grade]; i++) {
System.out.print("*");
}
System.out.println();
}
}
public static void main(String[] args) {
int[] grades = {7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6};
System.out.println("Grades: " + Arrays.toString(grades));
System.out.printf("Mean: %.2f%n", mean(grades));
System.out.println("Median: " + median(grades));
System.out.println("Mode: " + mode(grades));
System.out.println("Above the mean: " + countAbove(grades, mean(grades)));
// Proof that median didn't touch the original
System.out.println("Grades: " + Arrays.toString(grades));
System.out.println("\nHistogram:");
histogram(grades);
// With an even number of values
int[] evens = {4, 8, 6, 2};
System.out.println("\nMedian of " + Arrays.toString(evens) + " = " + median(evens));
}
}
Output
Grades: [7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6] Mean: 6.40 Median: 7.0 Mode: 7 Above the mean: 8 Grades: [7, 5, 9, 4, 10, 6, 7, 3, 8, 7, 5, 10, 2, 7, 6] Histogram: 0 | 1 | 2 | * 3 | * 4 | * 5 | ** 6 | ** 7 | **** 8 | * 9 | * 10 | ** Median of [4, 8, 6, 2] = 5.0
Look at the “Grades” line before and after calculating the median: identical. That’s what we were after by working with a copy. You can check it by hand: the sum of the 15 grades is 96, and 96 / 15 = 6.4 ✔. Sorted, the eighth one (the middle) is a 7 ✔.
Questions you might be asking
What would happen if median sorted data directly? Arrays.sort(data) would modify the original array, and in main the “Grades” line would show the data already sorted. It’s exactly the mistake from my anecdote: a method that changes something it shouldn’t. The wrong version would be this:
// ❌ WRONG: sorts the array it receives, the original ends up altered
static double median(int[] data) {
Arrays.sort(data);
...
}
Why does counts[grade]++ work? Because the grade (an int between 0 and 10) is a valid index of an array with 11 positions. Every time a grade shows up, we add one to its slot. Notice the array has 11 positions, not 10: to include both 0 and 10 you need eleven.
Why does mode use > and not >=? With a strict >, if two grades tie in frequency, we keep the first one we found, which is the lowest. If you change to >=, the highest would win. Trying this is a good exercise to understand how the operator affects the result.
What if a grade is out of range, for example 11? counts[11] doesn’t exist and an ArrayIndexOutOfBoundsException would be thrown. In a real program you’d validate the grades first (or make counts bigger). Here we assume correct data so we can focus on the technique.
Why is the median 7.0 and not 7? Because the method returns double: in the even case (4, 8, 6, 2 → 5.0) the median can have decimals, and a method must always return the same type.
Calculating the mean with integer division. If sum and data.length are int, sum / data.length gives 6 instead of 6.4. That’s why the program converts with (double) sum. (You already saw it in the theory with the average example.)
The mode with a frequency array only works when the values are small integers (like grades from 0 to 10). If the data could be any number, you’d need other structures, such as the collections you’ll see later.
Try it yourself
- Add
minimum(data)andmaximum(data)and show the range (maximum − minimum). - Count how many students failed (below 5) and how many got an outstanding grade (9 or more).
- Modify
histogramso it draws the rows from the highest grade to the lowest.
Typical mistakes in this practice
- Parallel arrays of different sizes. It compiles and fails at runtime with
ArrayIndexOutOfBoundsException. - Swapping row and column indexes when summing or transposing. It gives no error; it gives wrong results.
- Forgetting that a method modifies the array it receives. If you don’t want to alter the original, work on a copy with
Arrays.copyOf. - Comparing with
==Strings or arrays instead ofequalsorArrays.equals. - Integer division when calculating means.
Summary
- Parallel arrays let you store related data under the same index, but they must have the same length.
- A matrix is an array of arrays:
m.lengthis the rows andm[r].lengththe columns of rowr; in operations by rows or by columns, the index that rules is different. - If a method needs to sort or modify data without touching the original, work on a copy.
- Using the value as an index (
counts[grade]++) is a very powerful technique for counting frequencies. - Returning
-1for “not found” is a common and safe convention.
Checklist: have you practised it?
- Have you run the three programs and checked the outputs by hand?
- Can you explain why
sellmodifies the stock without returning it? - Do you know which index rules when summing by rows and which when summing by columns?
- Do you know why
medianworks on a copy? - Have you tried to solve the “Try it yourself” parts without peeking?
If you answered yes to almost everything, you’re ready for the next step: the arrays exercises, where I no longer give you the code ready-made. See you there!
Next step: exercisesArrays in Java — exercises with reversing, rotating, removing duplicates and merging (and a bug hiding where you least expect it)Did this article help you? Here is how you can support SergioLearns →
