Arrays in Java — from Python lists to fixed size, indexes and references
I still remember the first time one of my Java programs stopped dead with a red message saying ArrayIndexOutOfBoundsException. It was a loop going through an array of grades, and I was convinced the code was perfect: I had written i <= grades.length. I came from Python, where a mistake like that would have given me an IndexError and that would have been it, but Java made me stop and understand why the last index is length - 1. It took me a good while to realise the problem wasn’t the loop, it was that I hadn’t understood what an array is underneath. This article is so it doesn’t happen to you.
We’ll see what an array is, how to create it, how to loop over it, why two “equal” arrays sometimes aren’t, and what the Arrays class does. If you’re coming from Python, we’ll compare with lists and tuples along the way, because that’s where half the confusion comes from. And if you haven’t yet seen how methods work, it’s worth going through Methods in Java first: we’ll use them at the end.
What an array is (and how it compares to a Python list)
An array is a collection of values of the same type, stored one after another, with a fixed size that is decided when you create it. Think of an egg carton: it has a set number of slots, they all hold the same kind of thing (eggs), and every slot has a position.
Compared with Python, keep these three differences in mind:
- All elements have the same type. An
int[]only holds integers. In a Python list you can mix numbers and text; here you can’t. - The size is fixed. There’s no
appendorremove. If you need more slots, you have to create a bigger array. (For a variable size, Java hasArrayList, which you’ll meet later in collections.) - Elements can be modified. In that sense it’s like a list, not like a tuple.
A summary to keep handy:
| Python list | Python tuple | Java array | |
|---|---|---|---|
| Mixed types | Yes | Yes | No, a single type |
| Changes size | Yes | No | No |
| Elements can be modified | Yes | No | Yes |
Declaring and creating an array
In Java you have to tell apart two steps that you don’t see in Python: declaring the variable (saying what kind of array it is) and creating the array (reserving the slots). The type is followed by square brackets:
int[] grades; // declaration: "grades" will be an array of integers (it doesn't exist yet)
There are two ways to create it. The first is to give the values directly, between curly braces:
int[] grades = {7, 5, 9, 4, 10}; // 5 slots, already filled in
The second is to give only the size with new, and fill it in afterwards:
int[] grades = new int[5]; // 5 slots, all of them are 0 grades[0] = 7; grades[1] = 5;
And what do the slots hold if I don’t fill them?
Java never leaves a slot “without a value”: when you create the array with new, every element gets the default value of its type:
| Element type | Default value |
|---|---|
int, long, short, byte | 0 |
double, float | 0.0 |
boolean | false |
char | the null character ('\u0000') |
String and any object | null (“points to nothing”) |
int[] integers = new int[3]; boolean[] flags = new boolean[3]; String[] names = new String[3]; System.out.println(integers[0]); // 0 System.out.println(flags[0]); // false System.out.println(names[0]); // null
A freshly created String array doesn’t contain empty strings: it contains null. If you try names[0].length() before assigning anything to it, you’ll get a NullPointerException. It’s a classic mistake.
Accessing elements: indexes and length
Just like in Python, indexes start at 0. In an array of 5 elements, the valid positions are 0, 1, 2, 3 and 4.
int[] grades = {7, 5, 9, 4, 10};
int first = grades[0]; // 7
grades[3] = 6; // now the array is {7, 5, 9, 6, 10}
int howMany = grades.length; // 5
int last = grades[grades.length - 1]; // 10
Notice that length has no parentheses: in Python you wrote len(list), here it’s grades.length. It’s an attribute of the array, not a method. (With String it’s the other way round: text.length() does have parentheses. One of Java’s little traps.)
And an important difference from Python: negative indexes don’t exist. In Python list[-1] is the last element; in Java, grades[-1] is an error. For the last one you have to write grades[grades.length - 1].
ArrayIndexOutOfBoundsException. If you ask for a position that doesn’t exist, the program stops:
grades[5] → Index 5 out of bounds for length 5
With 5 elements, the last index is 4, not 5. The rule: the last index is always length - 1. It’s exactly what happened to me with that loop; you’ll see it calmly in the next section.
Looping over an array
There are two ways, just like in Python: by index or by value.
By index: the classic for
int[] grades = {7, 5, 9, 4, 10};
for (int i = 0; i < grades.length; i++) {
System.out.println("Position " + i + ": " + grades[i]);
}
What matters is the condition: i < grades.length, with strictly less than. The last iteration happens with i = 4, and after adding one, i is 5 and the loop stops. If you write i <= grades.length, there’s an extra iteration with i = 5 and the exception from before appears.
The most common mistake when looping over arrays: writing <= instead of < in the for condition. The program compiles without any problem and fails when it runs, right on the last iteration.
By value: the for-each
If you only need to read each value and you don’t care about its position, Java has a shorter version, equivalent to Python’s for value in list:
int sum = 0;
for (int grade : grades) { // "for each grade in grades"
sum += grade;
}
System.out.println("Sum: " + sum);
You read it as: “for each int called grade inside grades“. The colon : replaces Python’s word in.
Which one do I use? If you need the index (to know the position, to walk two arrays at once, or to modify elements), use the classic for. If you’re only going to read the values, use the for-each. It has a catch: in a for-each over int, the variable grade is a copy of each value, so doing grade = 0 inside the loop doesn’t change the array.
The point that confuses people most: an array is a reference
This is the hardest thing at first and, at the same time, what will save you the most mistakes if you understand it well. Look at this code and think about what you expect it to print:
int[] grades = {7, 5, 9};
int[] copy = grades; // does this copy the array?
copy[0] = 99;
System.out.println(grades[0]); // 7 or 99?
It prints 99. And it isn’t a bug: it’s how arrays work. A variable of type int[] doesn’t hold the numbers: it holds the address where the array is in memory. It’s like the address of a house. When you write int[] copy = grades;, you copy the address, not the house. Now there are two variables pointing to the same house, and if someone moves the furniture, both of them see it.
With plain numbers it doesn’t happen: int b = a; copies the value and from then on they’re independent. Arrays (and all objects) behave differently. If you want a real copy, you have to create another array:
import java.util.Arrays;
int[] grades = {7, 5, 9};
int[] copy = Arrays.copyOf(grades, grades.length); // new, independent array
copy[0] = 99;
System.out.println(grades[0]); // 7 (the original doesn't change)
System.out.println(copy[0]); // 99
Why == doesn’t work to compare arrays
The same idea explains another stumble. The == operator compares addresses, not contents:
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a == b); // false (they are two different arrays)
System.out.println(Arrays.equals(a, b)); // true (same contents)
It’s the same story as == versus .equals() with String: to compare contents, you use a method. For arrays, Arrays.equals(a, b).
And if I print an array directly, what comes out?
int[] grades = {7, 5, 9};
System.out.println(grades); // [I@49476842 (not the numbers!)
You get something like [I@49476842: the array’s type ([I means “array of int”) and a memory address. To see the contents, use Arrays.toString(grades), which gives [7, 5, 9].
Printing an array with println(array). If you see something like [I@6d06d69c, it isn’t a bug in your program: you just need Arrays.toString(...).
This idea of “the variable holds an address” will come back with classes and objects: String, and any object you create, work the same way. If you understand it here with arrays, you’re already halfway through that topic.
Arrays and methods
What we’ve just seen has a direct consequence if you already know methods: when you pass an array to a method, the address is passed, not a copy. That is, the method can modify your original array.
static void raiseByOne(int[] data) {
for (int i = 0; i < data.length; i++) {
data[i]++; // modifies the original array
}
}
public static void main(String[] args) {
int[] grades = {7, 5, 9};
raiseByOne(grades);
System.out.println(Arrays.toString(grades)); // [8, 6, 10]
}
With a single int this doesn’t happen (the method receives a copy of the value). With an array, it does. It’s very useful, but also a source of mistakes: if a method “sorts” or “changes” your array without warning, you’ll be surprised later. A good habit is to make it clear in the method name whether it modifies or only calculates (raiseByOne modifies; average only calculates).
Multi-dimensional arrays
In Python, a table is represented with a list of lists. In Java, with an array of arrays, declared with one pair of square brackets per dimension:
int[][] table = {
{10, 15, 20, 30, 40},
{12, 14, 16, 18},
{11, 17, 23, 29, 31}
};
System.out.println(table[1][3]); // 18 → row 1, column 3
The way to think about it: table is an array of 3 elements, and each element is itself an array of integers. That’s why table[1] is the whole second row, and table[1][3] is element 3 of that row.
You can also create an empty table by giving rows and columns:
int[][] table = new int[2][3]; // 2 rows, 3 columns, everything at 0 table[1][2] = 5; System.out.println(Arrays.deepToString(table)); // [[0, 0, 0], [0, 0, 5]]
Notice that to print a table you use deepToString (“deep”), because the plain toString would only know how to show the addresses of the rows.
Since each row is an independent array, rows can have different lengths (in the example above, 5, 4 and 5 elements). That’s why, when looping, the length of each row is asked with table[i].length and not with a single common length.
Looping over a table
You need two nested loops: the outer one goes through the rows, the inner one goes through the elements of each row.
// By index
for (int row = 0; row < table.length; row++) {
for (int col = 0; col < table[row].length; col++) {
System.out.print(table[row][col] + " ");
}
System.out.println(); // new line after finishing each row
}
// By value
for (int[] row : table) { // each "row" is an int[]
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}
In the for-each, the outer variable must be of the type one level down: if table is int[][], each row is an int[]. It’s the only rule to remember so you don’t get the types wrong.
The Arrays class: ready-made tools
Java comes with a class called Arrays (in the java.util package, which is why you have to write import java.util.Arrays; at the top of the file) with very common operations that aren’t worth programming by hand. The main ones:
| Method | What it does |
|---|---|
Arrays.toString(a) | Returns the contents as text: "[10, 15, 20]" |
Arrays.sort(a) | Sorts the array from smallest to largest (modifies the original) |
Arrays.binarySearch(a, x) | Looks for x in an already sorted array and returns its position |
Arrays.copyOf(a, n) | Creates a new array of length n with the data from a |
Arrays.copyOfRange(a, from, to) | Copies a slice (to is not included) |
Arrays.equals(a, b) | Compares the contents of two arrays |
Arrays.fill(a, x) | Fills the whole array with the value x |
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] data = {80, 70, 60, 50, 40, 30, 20, 10};
Arrays.sort(data);
System.out.println(Arrays.toString(data));
// [10, 20, 30, 40, 50, 60, 70, 80]
System.out.println(Arrays.binarySearch(data, 30)); // 2 (it's at position 2)
System.out.println(Arrays.binarySearch(data, 35)); // -4 (it's not there)
int[] slice = Arrays.copyOfRange(data, 2, 5);
System.out.println(Arrays.toString(slice)); // [30, 40, 50]
int[] bigger = Arrays.copyOf(data, 10); // the new slots are filled with 0
System.out.println(Arrays.toString(bigger));
// [10, 20, 30, 40, 50, 60, 70, 80, 0, 0]
int[] sevens = new int[5];
Arrays.fill(sevens, 7);
System.out.println(Arrays.toString(sevens)); // [7, 7, 7, 7, 7]
}
}
Why does binarySearch return -4?
When the value isn’t there, binarySearch returns -(insertion point) - 1, where the “insertion point” is the position where it should be to keep the order. Searching for 35 in [10, 20, 30, 40, ...], it would go at position 3 (between 30 and 40): -3 - 1 = -4. The practical takeaway: if the result is negative, it isn’t there; if it’s 0 or more, that’s its position.
binarySearch only works if the array is already sorted. If you use it on an unsorted array, it doesn’t give an error, but the result means nothing. Sort first with Arrays.sort.
Another catch: Arrays.equals compares only one level. If the elements are themselves arrays (a table), use Arrays.deepEquals, just like deepToString for printing.
Questions you might be asking
Can I add an element to an array?
No: the size is fixed. What you do is create a bigger array (with Arrays.copyOf(a, a.length + 1)) and put the new value in the last slot. If you need to do this often, it’s a sign you want an ArrayList.
Why do I have to say the size when I create it?
Because Java reserves a contiguous block of memory of that size. That’s what makes accessing grades[3] so fast: it knows exactly where it is without searching.
What’s the difference between an empty array and null?
new int[0] is a real array with zero elements (its length is 0 and you can loop over it with no problem). null means the variable doesn’t point to any array: if you do .length on it, you get a NullPointerException.
Can I create an array with a variable size, like new int[n]?
Yes. The size can be any expression, such as a variable read with Scanner. What can’t change is the size after it’s been created. If n is negative, it fails with NegativeArraySizeException.
A complete example
Let’s put everything together in a small program: it calculates the highest grade and the average of a group’s grades using methods, and then the average of each group in a table. Read the comments as you go; they explain the reasoning behind each decision.
import java.util.Arrays;
public class Grades {
// Returns the highest grade. Starts from the first element and compares with the rest.
static int maximum(int[] data) {
int max = data[0]; // assume the first one is the biggest
for (int i = 1; i < data.length; i++) { // start at 1: index 0 is already covered
if (data[i] > max) {
max = data[i];
}
}
return max;
}
// Returns the average. The (double) avoids integer division: 41 / 6 would give 6, not 6.83
static double average(int[] data) {
int sum = 0;
for (int n : data) { // for-each: we only read, we don't modify
sum += n;
}
return (double) sum / data.length;
}
// This method DOES modify the original array (that's why it uses the classic for)
static void raiseByOne(int[] data) {
for (int i = 0; i < data.length; i++) {
data[i]++;
}
}
public static void main(String[] args) {
int[] grades = {7, 5, 9, 4, 10, 6};
System.out.println("Grades: " + Arrays.toString(grades));
System.out.println("Highest: " + maximum(grades));
System.out.println("Average: " + average(grades));
raiseByOne(grades);
System.out.println("After raising: " + Arrays.toString(grades));
// Three groups with a different number of students (rows of different length)
int[][] groups = {
{7, 5, 9},
{4, 10},
{6, 8, 8, 3}
};
for (int g = 0; g < groups.length; g++) {
// groups[g] is an int[]: we can pass it straight to the average method
System.out.println("Group " + g + " (" + groups[g].length + " students): " + average(groups[g]));
}
}
}
Output:
Grades: [7, 5, 9, 4, 10, 6] Highest: 10 Average: 6.833333333333333 After raising: [8, 6, 10, 5, 11, 7] Group 0 (3 students): 7.0 Group 1 (2 students): 7.0 Group 2 (4 students): 6.25
See how nicely the pieces fit: average works the same with the grades array as with groups[g], because each row of the table is an array of integers. That’s what it means for a table to be “an array of arrays”.
See it with Python Tutor
References are much easier to understand by seeing them than by reading about them. Go to pythontutor.com/java.html, paste this code, and step through it with “Next”:
public class Main {
public static void main(String[] args) {
int[] grades = {7, 5, 9};
int[] alias = grades; // copy or same house?
alias[0] = 99;
int[] copy = new int[grades.length];
for (int i = 0; i < grades.length; i++) {
copy[i] = grades[i]; // real copy, element by element
}
copy[1] = 0;
System.out.println(grades[0]);
System.out.println(copy[1]);
}
}
What to look for:
- After
int[] grades = {7, 5, 9};, on the right you’ll see one array and the variablegradeswith an arrow pointing to it: that arrow is the “address”. - After
int[] alias = grades;, a second arrow appears pointing to the same array, not a new one. - When
alias[0] = 99;runs, the 7 turns into 99 in the only array that exists:grades[0]is also 99. - With
new int[grades.length], a second, independent array appears, and the loop copies the values one by one. Nowcopy[1] = 0doesn’t affectgrades.
Summary
- An array holds values of the same type and has a fixed size; its elements can be modified.
- It’s created with braces (
{1, 2, 3}) or withnew type[size]; if you don’t fill it in, the slots hold0,0.0,falseornulldepending on the type. - Indexes go from
0tolength - 1;lengthhas no parentheses and there are no negative indexes. - You loop with the classic
for(when you need the index or to modify) or with the for-each (read only). Theforcondition:i < array.length. - An array variable holds a reference: assigning (
b = a) doesn’t copy, and==doesn’t compare contents. UseArrays.copyOfandArrays.equals. - When you pass an array to a method, the method can modify it.
- A table is an array of arrays (
int[][]), it’s looped over with two loops, and its rows can have different lengths. - The
Arraysclass (toString,sort,binarySearch,copyOf,copyOfRange,equals,fill) saves you from reinventing the wheel.
Checklist: do you have it clear?
- Can you declare an array and create it both ways (braces and
new)? - Can you say what value each slot will have if you don’t fill it in?
- Can you explain why
grades[grades.length]fails? - Do you know why
int[] b = a;doesn’t make a copy? - Can you compare two arrays by contents and print one on screen?
- Can you loop over a table with two loops, by index and by value?
If any point left you with doubts, go back to its section before moving on. In the next article we’ll put all this into practice with complete programs.
Keep goingAll Java (en) articlesDid this article help you? Here is how you can support SergioLearns →
