Contents
- Declaration and Initialization
- Accessing and Iterating
- Multi-dimensional Arrays
- Arrays Utility Class
- Arrays vs ArrayList
An array is declared with the element type followed by [] and then the variable name. Its size is fixed at the moment new is called and cannot change afterward. Primitive elements are zero-initialized (0, false, 0.0); object elements default to null. You can supply the values inline with an initializer shorthand { v1, v2, ... } — the compiler infers the size. Arrays themselves are objects in Java, so they are always allocated on the heap and assigned by reference.
// Declaration — type followed by []
int[] numbers; // preferred style
String[] names;
double[] prices;
// Initialization with new
numbers = new int[5]; // default values: 0 for int, null for objects
names = new String[3]; // [null, null, null]
// Declaration + initialization together
int[] scores = new int[]{95, 87, 72, 68, 91};
// Shorthand initializer (only valid at declaration)
int[] scores2 = {95, 87, 72, 68, 91};
String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri"};
// Length — fixed at creation, cannot change
System.out.println(scores.length); // 5 (note: length not length())
// Array of objects
Person[] people = new Person[3];
people[0] = new Person("Alice", 30);
people[1] = new Person("Bob", 25);
// people[2] is null until assigned
// Array of arrays (not necessarily rectangular)
int[][] jagged = new int[3][];
jagged[0] = new int[]{1};
jagged[1] = new int[]{2, 3};
jagged[2] = new int[]{4, 5, 6};
Elements are accessed by their zero-based index using bracket notation: arr[0] is the first element, arr[arr.length - 1] is the last. Accessing an out-of-bounds index at runtime throws ArrayIndexOutOfBoundsException. Use a classic for loop when you need the index, or a for-each loop when you only need the values. The Arrays.stream() method exposes the array as an IntStream (or typed stream for objects), giving access to sum, filter, map, and the rest of the Stream API.
int[] nums = {10, 20, 30, 40, 50};
// Index access — zero-based
System.out.println(nums[0]); // 10
System.out.println(nums[nums.length-1]); // 50 (last element)
nums[2] = 99; // mutate
// ArrayIndexOutOfBoundsException if index < 0 or >= length
// nums[5]; // throws ArrayIndexOutOfBoundsException
// Classic for loop
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
// Enhanced for-each (no index available)
for (int n : nums) {
System.out.print(n + " ");
}
// Stream API
int sum = Arrays.stream(nums).sum();
int max = Arrays.stream(nums).max().orElseThrow();
long count = Arrays.stream(nums).filter(n -> n > 20).count();
// Convert array to List (backed by array — fixed size)
List<String> list = Arrays.asList("a", "b", "c");
// list.add("d"); // throws UnsupportedOperationException
// For a mutable List:
List<String> mutable = new ArrayList<>(Arrays.asList("a", "b", "c"));
Java multi-dimensional arrays are arrays of arrays — each row is itself an independent array object. A 2D array int[][] m is indexed as m[row][col]. Because each row is a separate array, rows can have different lengths: this is called a jagged (ragged) array and is useful for triangular data or variable-length rows. For a rectangular matrix, declare all dimensions upfront: new int[rows][cols]. Three-dimensional and higher arrays follow the same pattern.
// 2D array — matrix representation
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Access: [row][col]
System.out.println(matrix[1][2]); // 6 (row 1, col 2)
// Iterate a 2D array
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
System.out.printf("%3d", matrix[row][col]);
}
System.out.println();
}
// Using streams with 2D array
Arrays.stream(matrix)
.flatMapToInt(Arrays::stream)
.forEach(System.out::println);
// 3D array
int[][][] cube = new int[3][3][3];
cube[0][1][2] = 42;
// Jagged (ragged) array — rows of different lengths
int[][] triangle = new int[5][];
for (int i = 0; i < triangle.length; i++) {
triangle[i] = new int[i + 1]; // row 0 has 1 element, row 4 has 5
}
java.util.Arrays is a collection of static helper methods that cover the most common array operations. Arrays.sort() sorts in-place using dual-pivot quicksort for primitives and timsort for objects. Arrays.binarySearch() requires a sorted array and returns the index (or a negative value if not found). Arrays.copyOf() and Arrays.copyOfRange() create new arrays without manual loops. Arrays.fill() initializes all elements to a value. Arrays.equals() compares element-by-element; use Arrays.deepEquals() for multi-dimensional arrays. Arrays.toString() / Arrays.deepToString() give readable string output.
import java.util.Arrays;
int[] arr = {5, 3, 8, 1, 9, 2, 7};
// sort — in-place, uses dual-pivot quicksort for primitives (O(n log n))
Arrays.sort(arr); // [1, 2, 3, 5, 7, 8, 9]
Arrays.sort(arr, 1, 4); // sort only indices 1-3 (exclusive end)
// sort with Comparator (object arrays only)
String[] words = {"banana", "apple", "cherry"};
Arrays.sort(words); // alphabetical
Arrays.sort(words, Comparator.reverseOrder()); // reverse
Arrays.sort(words, Comparator.comparingInt(String::length)); // by length
// binarySearch — array must be sorted first
int idx = Arrays.binarySearch(arr, 7); // returns index of 7, or negative if absent
// fill — set all elements to a value
int[] zeros = new int[5];
Arrays.fill(zeros, 42); // [42, 42, 42, 42, 42]
Arrays.fill(zeros, 1, 3, 99); // [42, 99, 99, 42, 42] (indices 1-2)
// copyOf — creates a new array (truncates or pads with defaults)
int[] copy = Arrays.copyOf(arr, 3); // first 3 elements
int[] copy2 = Arrays.copyOf(arr, 10); // padded with 0s
// copyOfRange — copy a slice
int[] slice = Arrays.copyOfRange(arr, 2, 5); // indices 2,3,4
// equals / deepEquals
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(Arrays.equals(a, b)); // true
System.out.println(a == b); // false (different references)
int[][] m1 = {{1,2},{3,4}};
int[][] m2 = {{1,2},{3,4}};
System.out.println(Arrays.deepEquals(m1, m2)); // true
// toString / deepToString
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 5, 7, 8, 9]
System.out.println(Arrays.deepToString(matrix)); // [[1, 2, 3], [4, 5, 6], ...]
Arrays are fixed-size and can hold primitive types directly (int[], double[]), which avoids boxing overhead and gives better cache locality. ArrayList<T> is dynamically resizable but stores boxed objects. Choose arrays when the size is known upfront and performance matters; choose ArrayList when you need add/remove or want to work with the Collections API. You can convert between the two: Arrays.asList() wraps an array in a fixed-size list; list.toArray() goes the other direction.
// Arrays: fixed size, can store primitives, slightly faster
int[] arr = new int[10];
// ArrayList: dynamic size, only object types, more convenient methods
List<Integer> list = new ArrayList<>();
list.add(1); list.add(2); list.add(3);
// Convert array ↔ List
Integer[] boxed = {1, 2, 3};
List<Integer> asList = Arrays.asList(boxed); // fixed-size backed list
List<Integer> mutable = new ArrayList<>(asList); // fully mutable
// List back to array
Integer[] backToArray = list.toArray(new Integer[0]); // idiomatic
int[] primitive = list.stream().mapToInt(Integer::intValue).toArray();
// When to use each:
// - Use arrays for fixed-size collections of primitives (int[], double[])
// for performance-critical code (avoids boxing overhead)
// - Use List<T> for dynamic collections, when you need add/remove,
// or when interacting with Collections API
Use Arrays.copyOf() or System.arraycopy() (lower-level, faster) to "resize" an array — they actually create a new array. System.arraycopy(src, srcPos, dest, destPos, length) is the most performant option for bulk copies.