Arrays
Arrays are Java's fixed-size, index-addressed containers of same-typed elements — objects in their own right, with a length that's set once at creation and never changes.
An array in Java is a fixed-length, ordered collection of elements of a single declared type, accessed by a zero-based integer index. Arrays are objects: they live on the heap, are accessed through references, have a length field (not a method), and — like every object — their default value when uninitialized is null. Once created, an array's size can never change; growing or shrinking means creating a new array and copying elements across.
Declaration, Creation, and Initialization
Declaring an array variable and creating the array object are two separate steps, though they're often combined in one statement. Java's preferred bracket placement is right after the type (int[] a), though the C-style int a[] is also legal for compatibility.
The bare {2, 3, 5, 7} literal form is only legal directly in a declaration; when passing an array as an argument or reassigning, you need the explicit new int[]{...} form.
int[] scores; // declaration only — scores is null until assigned
scores = new int[5]; // creation — a 5-element array, all elements default to 0
int[] ages = new int[3]; // declaration + creation combined
int[] primes = {2, 3, 5, 7}; // array literal — creation + initialization combined
int[] more = new int[]{1, 2, 3}; // equivalent explicit form, usable outside a declarationIndexing and Zero-Based Access
Array elements are accessed with square-bracket indexing, and indices start at 0 — the first element is a[0], and the last valid index of an array of length n is n - 1.
int[] a = {10, 20, 30};
System.out.println(a[0]); // 10 — first element
System.out.println(a[2]); // 30 — last element (length - 1)
a[1] = 99; // mutate an element in place
System.out.println(a[1]); // 99length, Default Values, and Fixed Size
length is a public final field on every array (not a method — no parentheses), fixed at the size given when the array was created. Elements you don't explicitly initialize get the type's default value: 0 for numeric primitives, false for boolean, '\u0000' for char, and null for any reference type.
An array's length is decided at the moment of creation and cannot change afterward — there is no resize or add method on a raw array. Java's growable list types (like ArrayList) are built on top of arrays precisely to hide this reallocation dance.
int[] nums = new int[4];
System.out.println(nums.length); // 4
System.out.println(nums[0]); // 0 — default for int
boolean[] flags = new boolean[3];
System.out.println(flags[0]); // false
String[] names = new String[2];
System.out.println(names[0]); // null — default for a reference typeBounds Checking
Every array access is bounds-checked by the JVM at runtime. Accessing an index less than 0 or greater than or equal to length throws ArrayIndexOutOfBoundsException immediately — Java never lets you silently read or write past the end of an array the way some lower-level languages do.
int[] a = {1, 2, 3};
try {
System.out.println(a[3]); // valid indices are 0, 1, 2
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught: " + e.getMessage());
}Arrays of Primitives vs. Arrays of Objects
An array of a primitive type stores the primitive values directly, inline, in the array's memory. An array of a reference type stores references — the array itself holds pointers to the actual objects, which live separately on the heap (or are null if never assigned).
Arrays themselves are always reference types, regardless of their element type — int[] is an object with a class, int[].class, even though its elements are primitives.
int[] primitiveArray = {1, 2, 3}; // values stored directly
String[] objectArray = new String[3]; // three null references, initially
objectArray[0] = new String("hi"); // now objectArray[0] references a String objectArray References and Aliasing
An array variable holds a reference to the array object, not the array's contents. Assigning one array variable to another copies the reference, not the elements — both variables then alias the same underlying array, and mutating through one is visible through the other.
int[] a = {1, 2, 3};
int[] b = a; // b now references the SAME array object as a
b[0] = 999;
System.out.println(a[0]); // 999 — a and b are aliases of one arrayCopying Arrays
To get an independent copy rather than an alias, you must explicitly copy the elements — via a loop, Arrays.copyOf, Arrays.copyOfRange, System.arraycopy, or clone(). All of these produce a shallow copy: for an array of objects, the new array holds copies of the references, not deep copies of the referenced objects themselves.
For an array of mutable objects, a shallow copy still shares the same underlying objects between the original and the copy — mutating an object reachable from one array affects the other too, since only the references were duplicated, not the objects.
import java.util.Arrays;
int[] original = {1, 2, 3};
int[] copy = Arrays.copyOf(original, original.length);
copy[0] = 999;
System.out.println(original[0]); // 1 — unaffected, this is a true independent copy
int[] cloned = original.clone(); // also a shallow copy, same effect for primitivesMultidimensional Arrays — a Preview
Java has no true native multidimensional array type; a "2D array" is really an array of arrays, and rows can even have different lengths (a "jagged" array). This topic is covered in full depth separately — the key thing to know here is that int[][] is simply int[] whose elements are themselves int[].
int[][] grid = new int[2][3]; // a 2x3 rectangular grid, all zeros
grid[0][1] = 5;
System.out.println(grid.length); // 2 — number of rows
System.out.println(grid[0].length); // 3 — length of the first rowThe Arrays Utility Class
java.util.Arrays provides static helper methods that raw arrays lack natively: printing, sorting, searching, filling, comparing, and converting to a List view.
Arrays.binarySearch requires the array to already be sorted; calling it on an unsorted array gives an unspecified, unreliable result.
import java.util.Arrays;
int[] a = {5, 3, 1, 4, 2};
Arrays.sort(a);
System.out.println(Arrays.toString(a)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.binarySearch(a, 4)); // 3 — index of 4 in the SORTED array
int[] filled = new int[4];
Arrays.fill(filled, 7);
System.out.println(Arrays.toString(filled)); // [7, 7, 7, 7]
int[] b = {1, 2, 3};
int[] c = {1, 2, 3};
System.out.println(b == c); // false — different array objects
System.out.println(Arrays.equals(b, c)); // true — same elements, compared properlyPractical Usage
Arrays are the right tool when you know the size up front (or rarely change it), need maximum performance for numeric data, or are interoperating with APIs that expect arrays (like main(String[] args), varargs, or many library methods). For growable, insertion-heavy, or type-rich collections, the Collections Framework (ArrayList, etc.) is usually a better fit and is built on arrays internally anyway.
static double average(int[] data) {
if (data.length == 0) return 0.0;
long sum = 0;
for (int v : data) sum += v;
return (double) sum / data.length;
}Edge Cases
A zero-length array (new int[0]) is perfectly valid and distinct from null — .length on it is 0, and iterating it simply does nothing. Comparing two arrays with == always compares references, never contents, even if the arrays hold identical elements. Calling .equals() on arrays (inherited from Object) also only checks reference identity, not content — this surprises many newcomers who expect value-based equality.
int[] x = {1, 2, 3};
int[] y = {1, 2, 3};
System.out.println(x.equals(y)); // false — Object.equals(), not content comparison
System.out.println(Arrays.equals(x, y)); // true — the correct way to compare contentsBest Practices
Prefer Arrays.toString/deepToString over manual printing loops. Use Arrays.equals/deepEquals instead of == or .equals() for content comparison. Return defensive copies (or unmodifiable views) from methods that expose an internal array as part of an object's state, to avoid callers mutating your internals through the returned reference. Validate indices before accessing arrays from untrusted input rather than relying solely on ArrayIndexOutOfBoundsException.
Complete Example
import java.util.Arrays;
public class ArrayDemo {
public static void main(String[] args) {
int[] readings = {23, 19, 42, 8, 15};
int max = readings[0];
for (int v : readings) {
if (v > max) max = v;
}
System.out.println("Max: " + max); // 42
int[] sorted = Arrays.copyOf(readings, readings.length); // independent copy
Arrays.sort(sorted);
System.out.println("Original: " + Arrays.toString(readings)); // unaffected
System.out.println("Sorted: " + Arrays.toString(sorted));
}
}Key Takeaways
Arrays are fixed-length, zero-indexed, and are objects on the heap — array variables hold references, not the elements themselves.
length is a field, not a method, and every access is bounds-checked, throwing ArrayIndexOutOfBoundsException on an invalid index.
Uninitialized elements get type-appropriate defaults: 0/false/'\u0000' for primitives, null for reference types.
Assigning one array variable to another aliases the same array; use Arrays.copyOf, System.arraycopy, or clone() for an independent (shallow) copy.
Compare array contents with Arrays.equals/deepEquals, never with == or the inherited Object.equals().
int[] a = {1,2,3}; int[] b = {1,2,3}; if (a == b) { /* expecting equal contents */ } if (Arrays.equals(a, b)) { /* correctly compares contents */ }
== on arrays compares references, not contents — two distinct arrays with identical elements are never == to each other.
int[] copy = original; // intending an independent copy copy[0] = 999; // unexpectedly also changes original[0] int[] copy = Arrays.copyOf(original, original.length); copy[0] = 999; // original is untouched
Plain assignment copies the reference, aliasing the same array — mutating through one variable is visible through the other. Use Arrays.copyOf/System.arraycopy/clone() for an independent copy.
int[] a = new int[3]; System.out.println(a[3]); // off-by-one — valid indices are 0..2
System.out.println(a[a.length - 1]); // last valid index is length - 1
The valid index range is 0 through length - 1 inclusive; using length itself as an index is a classic off-by-one bug that throws ArrayIndexOutOfBoundsException.
Is an array's length fixed at creation, and how does length differ from a method call syntactically?
Yes — an array's length is fixed permanently when it is created with new and can never change afterward. length is accessed as a.length, a public final field, not a method call, so there are no parentheses (unlike a String's a.length()).
What default value does each element of a newly created int[] array hold, and how does that differ for an array of a reference type like String[]?
int[] elements default to 0, following the same default-value rules as instance/static int fields. String[] (or any reference type array) elements default to null, since arrays are objects and uninitialized reference-type elements always default to null.
What happens when you assign one array variable to another, like int[] b = a;, and how would you instead get an independent copy?
The assignment copies only the reference, so a and b become aliases pointing to the exact same array object — mutating through either variable is visible through both. To get an independent copy, use Arrays.copyOf(a, a.length), System.arraycopy, or a.clone().
Why does a.equals(b) return false for two int arrays with identical elements, and what should you use instead?
Arrays don't override equals(), so they inherit Object.equals(), which only checks reference identity — the same thing == does. To compare contents, use Arrays.equals(a, b) for one-dimensional arrays or Arrays.deepEquals for nested/multidimensional arrays.
What exception is thrown when an array is accessed with an index outside its valid range, and what is the valid range for an array of length n?
ArrayIndexOutOfBoundsException is thrown; the valid range of indices is 0 through n - 1 inclusive, so index n itself (and any negative index) is out of bounds.
Does Arrays.copyOf produce a deep copy or a shallow copy of an array of objects? What does that distinction mean in practice?
It produces a shallow copy — the new array holds copies of the object references, not new copies of the objects themselves. So the two arrays are independent (you can reassign an element in one without affecting the other), but if both arrays contain a reference to the same mutable object, mutating that shared object through one array is visible through the other.
Why must Arrays.binarySearch only be called on a sorted array, and what happens if you call it on an unsorted one?
Binary search works by repeatedly halving a range under the assumption the data is in sorted order; calling it on an unsorted array does not throw an exception but produces an unspecified, unreliable result, since the algorithm's core assumption is violated.
Write a method reverseArray that returns a new int array containing the elements of the input array in reverse order, without modifying the original.
import java.util.Arrays; public class ReverseDemo { static int[] reverseArray(int[] input) { int[] result = new int[input.length]; for (int i = 0; i < input.length; i++) { result[i] = input[input.length - 1 - i]; } return result; } public static void main(String[] args) { int[] original = {1, 2, 3, 4, 5}; int[] reversed = reverseArray(original); System.out.println(Arrays.toString(original)); // [1, 2, 3, 4, 5] — unchanged System.out.println(Arrays.toString(reversed)); // [5, 4, 3, 2, 1] } } reverseArray allocates a brand-new array of the same length and copies each element from the opposite end of the input, leaving the original array untouched; result[0] gets input's last element, and so on.
What does this program print, and why do both println statements show the same values?
Both lines print "7 42 0". a and b reference the exact same array object (b = a copies only the reference), so mutations made through either variable — b[1] = 42 and a[0] = 7 — are visible through both variable names, since there is really only one array in memory.
What happens when this code runs, and what is the valid index range for arr?
It throws java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3. arr has length 3, so its valid indices are 0, 1, and 2 (0 through length - 1); index 3 is one past the last valid element, a classic off-by-one error.
int[] a = new int[3];
int[] b = a;
b[1] = 42;
a[0] = 7;
System.out.println(a[0] + " " + a[1] + " " + a[2]);
System.out.println(b[0] + " " + b[1] + " " + b[2]);int[] arr = {10, 20, 30};
System.out.println(arr[3]);