A Java array is a fixed-length, ordered run of values that all share one type, and you create one with a declaration like int[] scores = new int[4];. Once the array exists its length never changes, every slot arrives pre-filled with a default value, and you reach each element by a zero-based index.
What exactly is a Java array?
An array is part of the Java language itself rather than a class you import from a library, which is why arrays behave differently from most other objects you will meet. Under the hood it is a single, contiguous block of memory. The runtime knows the size of the block and the type of every cell in it. What it does not know is what your elements mean to you.
Picture a row of numbered lockers. The row is the array, each locker is an element, and the number painted on the door is the index. The row has a fixed number of lockers, and that number is scores.length. Turn up with a crowd and the row has no spare locker, so you get an error rather than a new one appearing.
Two details trip people up early. First, length is a field rather than a method, so you write scores.length and never scores.length(). Second, the variable you declare is only a reference: int[] scores does not hold the numbers, it points at a block that does. Copying that reference copies the pointer, not the block, and that distinction becomes the whole subject of how arrays flow through methods.
public class ArraysBasics {
public static void main(String[] args) {
int[] scores = new int[4];
scores[0] = 72;
scores[1] = 88;
scores[3] = 95;
System.out.println("length = " + scores.length);
for (int i = 0; i < scores.length; i++) {
System.out.println("scores[" + i + "] = " + scores[i]);
}
}
}Running that program prints the length first and then each slot in index order:
length = 4
scores[0] = 72
scores[1] = 88
scores[2] = 0
scores[3] = 95Slot 2 was never assigned, yet reading it still produced a sensible value. That is the default value for int, and it is worth noticing rather than ignoring.
How do you create a Java array?
There are two common forms, and they differ in one visible way: the first states the size up front, the second states the contents. Use the new form when the size comes from somewhere else, such as a count you read from a file. Use the initializer form when you already know the values.
public class ArrayInit {
public static void main(String[] args) {
String[] languages = { "Java", "Kotlin", "Scala" };
int[] weeks = new int[] { 1, 2, 3 };
double[] rates = { 0.15, 0.25 };
System.out.println(languages.length + " " + languages[0] + " " + languages[2]);
System.out.println(weeks.length + " " + weeks[1]);
System.out.println(rates.length + " " + rates[0]);
System.out.println("char[] -> " + new char[2].length);
}
}3 Java Scala
3 2
2 0.15
char[] -> 2Note the difference between new int[] { 1, 2, 3 } and new int[3]. The first builds an array and immediately fills it with the values you listed, so its length is 3 because you wrote three values. The second reserves three empty cells, and their contents are whatever the defaults happen to be. Writing new int[] { 1, 2, 3 } without the braces is a compile error, as is new int[3] { 1, 2, 3 }.
Compile and run the example from an empty folder like this:
javac ArrayInit.java
java ArrayInitWhat values does a brand-new array contain?
Every slot in a newly created array is set to the default value for its element type. For primitives that default is zero-ish: 0 for integers, 0.0 for floating point, false for booleans, and the null character for char. For reference types such as String the default is null, which means no object yet rather than an empty string.
public class ArrayDefaults {
public static void main(String[] args) {
int[] counts = new int[3];
double[] prices = new double[3];
boolean[] flags = new boolean[3];
String[] names = new String[3];
System.out.println("counts[0] = " + counts[0]);
System.out.println("prices[0] = " + prices[0]);
System.out.println("flags[0] = " + flags[0]);
System.out.println("names[0] = " + names[0]);
System.out.println("names[0] == null -> " + (names[0] == null));
}
}counts[0] = 0
prices[0] = 0.0
flags[0] = false
names[0] = null
names[0] == null -> trueThe null default is the one that bites. A String[] filled with null will throw a NullPointerException the moment you call a method on one of its elements, and the stack trace will point at a line that looks perfectly innocent. If you want empty strings, either assign them explicitly or remember that new String[3] gives you three nulls, not three empty strings.
Why do indices start at zero, and what happens past the end?
Because an index is an offset from the start of the block. The first cell sits zero bytes in, the second sits one element-width in, and so on. It also means the last valid index is always length - 1, which is exactly where off-by-one errors come from: writing <= length in a loop condition reads one cell too far.
Java checks every index at runtime and throws ArrayIndexOutOfBoundsException when it is wrong. The exception is unchecked, so the compiler will not force you to handle it, which is exactly why you should think about bounds rather than rely on the crash.
public class ArrayBounds {
public static void main(String[] args) {
String[] playlist = { "Lo-fi beats", "Synthwave", "Jazzhop" };
int index = 5;
try {
System.out.println(playlist[index]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught: " + e.getClass().getSimpleName());
}
System.out.println("valid range is 0.." + (playlist.length - 1));
}
}Caught: ArrayIndexOutOfBoundsException
valid range is 0..2A negative index fails the same way, because offsets are measured forwards from the start. The safe habit is to treat length as the exclusive upper bound and loop with i < something.length.
Can an array hold objects as well as primitives?
Yes, and this is where the reference-versus-value distinction shows up most clearly. An int[] stores the four bytes of each number directly inside the array block. A Task[] stores four references, and each reference points at an object living somewhere else in memory. Changing a field on the object changes what the array sees, because the array is pointing at that very object.
public class ArrayOfObjects {
static class Task {
final String title;
boolean done;
Task(String title) {
this.title = title;
}
}
public static void main(String[] args) {
Task[] board = { new Task("Write intro"), new Task("Record demo") };
System.out.println("size = " + board.length);
System.out.println("board[0] = " + board[0].title);
board[0].done = true;
System.out.println("board[0] done -> " + board[0].done);
System.out.println("board[1] done -> " + board[1].done);
}
}size = 2
board[0] = Write intro
board[0] done -> true
board[1] done -> falseSetting board[0].done affects only that one task, because each slot holds its own reference to its own object. Put the same object in two slots and changing it through one slot changes what the other slot sees too, since both point at the same object.
One more constraint: every element of an array has the same declared type. An Object[] can hold a mix of everything, but every element comes back as Object and needs a cast, which is usually a sign you wanted a different structure entirely.
When should you pick an array instead of a list?
Arrays and collections overlap, so it helps to compare them directly rather than by folklore.
| Property | Array | ArrayList |
|---|---|---|
| Size | Fixed the moment you create it | Grows and shrinks as you add and remove |
| Element types | One concrete type, plus its subtypes | Generics, checked at compile time |
| Reading an element | Direct index access, no method call | Index access through a method call |
| Putting values in | You assign each slot yourself | Built-in add, insert and remove operations |
| Best fit | Fixed-size numeric data, index-addressed lookup | Collections whose contents change over time |
Pick an array when the size is genuinely fixed and you want index access with the least ceremony. Pick a list when the final size is unknown, or when you need to insert and remove. The collections module later in this tutorial covers the list side in depth.
What to remember about arrays
- An array has a fixed length decided at creation time; you cannot grow it in place.
- Indices run from 0 to
length - 1, and an out-of-range index throwsArrayIndexOutOfBoundsException. lengthis a field, so it isscores.lengthwith no parentheses.- New slots hold the type's default value: zeros,
false, andnullfor reference types. - Declaring an array variable creates a reference; the block itself is separate.
- Every element shares one declared type, which is what keeps your code type-safe.
Try it yourself: build a small program named SeatBook that stores six exam marks in an int[] called marks. Create the array with the new form, assign three of the six slots by hand, then print the whole array one element per line using a for loop with marks.length as the bound. Finish by attempting to read marks[6] inside a try block, catch ArrayIndexOutOfBoundsException, and print the exception's class name. Before you run it, write down what you expect the three unassigned slots to print, then check your prediction against the real output.