☕ Java
Array Initialization
An array is a fixed-size, ordered collection of elements of the same type stored in contiguous memory locations. Arrays are the most fundamental data structure in Java — they underlie ArrayList, String's internal character storage, and most collection implementations. Understanding arrays deeply means understanding how they are allocated on the heap, how element access works in constant time, why their size is fixed, and what the trade-offs are between arrays and higher-level collection types. This entry covers every form of array declaration and initialization, multidimensional arrays, the default value guarantee, array covariance, and the practical considerations for choosing between arrays and collections.
Array Fundamentals — What Arrays Are
An array in Java is an object allocated on the heap that stores a fixed number of values of a single type in contiguous memory. The contiguity guarantee is the source of arrays' most important property: constant-time random access. Given the base address of the array and an index, the JVM computes the address of any element with a single arithmetic operation — base + (index × element_size). No traversal, no pointer following, just arithmetic. This O(1) access cost is what makes arrays the performance foundation of virtually every other data structure.
The size of an array is fixed at allocation time and stored in the array object's length field. Once allocated, the array cannot grow or shrink. Attempting to access an index outside [0, length-1] throws ArrayIndexOutOfBoundsException at runtime. The length field is final — it can be read but not written. These constraints exist because the contiguity guarantee would be impossible to maintain if the array could change size after allocation.
Every array in Java is an object, even arrays of primitive types. The array object itself is on the heap, and it has an implicit class hierarchy: every array type is a subtype of Object and implements Cloneable and Serializable. An int[] is-an Object. A String[] is-an Object. This means arrays can be stored in Object variables and passed to methods accepting Object. It also means arrays have an identity — two array variables can reference the same array object, and mutation through one variable is visible through the other.
Java
// ── What an array looks like in memory ───────────────────────────────
//
// int[] scores = new int[5];
//
// Stack Heap
// ┌──────────┐ ┌───────────────────────────────────────┐
// │ scores ──┼───────►│ int[] object │
// └──────────┘ │ length = 5 │
// │ [0]=0 [1]=0 [2]=0 [3]=0 [4]=0 │
// └───────────────────────────────────────┘
//
// Element address = baseAddress + (index × 4) // 4 bytes per int
// scores[3] address = base + 12 // one arithmetic op
// ── Fixed size — cannot change after allocation ───────────────────────
int[] arr = new int[5];
System.out.println(arr.length); // 5 — always 5
// arr.length = 10; // COMPILE ERROR — length is final
// ── Arrays are objects ────────────────────────────────────────────────
int[] a = new int[3];
Object o = a; // array is-an Object
System.out.println(a instanceof Object); // true
System.out.println(a instanceof Cloneable); // true
// ── Two variables can reference the same array ────────────────────────
int[] x = {1, 2, 3};
int[] y = x; // y and x point to the same array object
y[0] = 99;
System.out.println(x[0]); // 99 — mutation through y is visible via x
System.out.println(x == y);// true — same object in memory
// ── Copying vs aliasing ───────────────────────────────────────────────
int[] z = Arrays.copyOf(x, x.length); // z is a NEW array
z[0] = 0;
System.out.println(x[0]); // 99 — x is unaffected — z is independent
System.out.println(x == z);// false — different objectsDeclaration and Initialization Syntax
Java provides three distinct ways to initialize an array, each suited to different situations. The new expression allocates an array of a specified size and fills it with default values — zeros for numeric types, false for boolean, null for reference types. The array literal syntax combines allocation and initialization in one step, with the compiler inferring the size from the number of provided values. The shorthand initializer (without new) is available only in declarations, not in assignments.
The default value guarantee is absolute and worth understanding precisely. When you write new int[100], all 100 elements are guaranteed to be 0 before any code touches them. This is not an implementation detail — it is specified by the Java Language Specification. This guarantee eliminates an entire category of bugs present in C and C++ programs where uninitialized array elements contain garbage values from previous memory use.
The two bracket placement positions for array declarations (int[] arr and int arr[]) are both legal and identical in meaning. The conventional Java style places brackets after the type (int[]) rather than after the variable name (int arr[]). The second form is a legacy from C syntax and is discouraged in Java.
Java
// ── Form 1: new expression — size specified, defaults fill ───────────
int[] zeros = new int[5]; // [0, 0, 0, 0, 0]
boolean[] flags = new boolean[3]; // [false, false, false]
String[] names = new String[4]; // [null, null, null, null]
double[] floats = new double[2]; // [0.0, 0.0]
// ── Form 2: array literal — size inferred from values ─────────────────
int[] primes = {2, 3, 5, 7, 11}; // size = 5, inferred
String[] days = {"Mon","Tue","Wed","Thu","Fri","Sat","Sun"};
double[] rates = {0.01, 0.025, 0.05};
// ── Form 3: new with literal — required for reassignment ──────────────
int[] data;
data = new int[]{10, 20, 30}; // 'new int[]' required outside declaration
// data = {10, 20, 30}; // COMPILE ERROR — shorthand only in declaration
// ── Bracket position — both legal, prefer first style ─────────────────
int[] preferred = new int[5]; // idiomatic Java
int legacy[] = new int[5]; // C-style legacy — avoid
// ── Default values by type ────────────────────────────────────────────
// Type Default
// ─────────────────────────
// byte 0
// short 0
// int 0
// long 0L
// float 0.0f
// double 0.0d
// char ' ' (null character)
// boolean false
// reference null
// ── Verify defaults ───────────────────────────────────────────────────
int[] ints = new int[3];
char[] chars = new char[3];
boolean[] bools = new boolean[3];
String[] strings = new String[3];
System.out.println(ints[0]); // 0
System.out.println(chars[0]); // '' (null char — not visible)
System.out.println(bools[0]); // false
System.out.println(strings[0]); // null
// ── Size from expression ──────────────────────────────────────────────
int n = 10;
int[] dynamic = new int[n]; // size from variable
int[] computed = new int[n * 2]; // size from expression
int[] sized = new int[n + 5];Multidimensional and Jagged Arrays
Java does not have true multidimensional arrays. What Java calls a two-dimensional array is actually an array of arrays — a one-dimensional array whose elements are references to other one-dimensional arrays. This distinction matters because it means "rows" can have different lengths (jagged arrays), and because each row is an independent array object on the heap with its own allocation.
A true two-dimensional array in languages like C stores all elements in a single contiguous block of memory — row after row. Java's array-of-arrays uses one allocation per row, and those allocations can be at different memory addresses. For small arrays this difference is invisible; for large numerical computations where cache locality matters, it can be a significant performance consideration, and libraries like Apache Commons Math and nd4j use flat one-dimensional arrays with manual index arithmetic to achieve C-like performance.
The syntax for multidimensional arrays follows the same rules as one-dimensional arrays, with additional bracket pairs for each dimension. The sizes for inner dimensions can be specified independently, enabling jagged arrays — arrays where each row has a different number of columns. This is useful for triangular matrices, adjacency lists, and any structure where rows naturally have unequal sizes.
Java
// ── 2D array: array of arrays ────────────────────────────────────────
int[][] matrix = new int[3][4]; // 3 rows, 4 columns each
//
// matrix → [ ref0 → [0,0,0,0]
// ref1 → [0,0,0,0]
// ref2 → [0,0,0,0] ]
//
// Three separate array objects on the heap
System.out.println(matrix.length); // 3 — number of rows
System.out.println(matrix[0].length); // 4 — columns in row 0
// ── 2D literal ────────────────────────────────────────────────────────
int[][] grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(grid[1][2]); // 6 — row 1, column 2
// ── Jagged array — rows with different lengths ────────────────────────
int[][] triangle = new int[5][]; // 5 rows, column count not specified yet
for (int i = 0; i < triangle.length; i++) {
triangle[i] = new int[i + 1]; // row i has (i+1) elements
}
// triangle[0] = [0]
// triangle[1] = [0, 0]
// triangle[2] = [0, 0, 0]
// triangle[3] = [0, 0, 0, 0]
// triangle[4] = [0, 0, 0, 0, 0]
// ── Pascal's triangle using jagged array ──────────────────────────────
int[][] pascal = new int[6][];
for (int row = 0; row < pascal.length; row++) {
pascal[row] = new int[row + 1];
pascal[row][0] = pascal[row][row] = 1;
for (int col = 1; col < row; col++) {
pascal[row][col] = pascal[row-1][col-1] + pascal[row-1][col];
}
}
// ── 3D array ──────────────────────────────────────────────────────────
int[][][] cube = new int[3][3][3]; // 3×3×3 cube
cube[0][1][2] = 42;
// ── Flat array with manual indexing — cache-friendly alternative ───────
// For high-performance 2D work, a flat array avoids pointer chasing
int rows = 100, cols = 100;
int[] flat = new int[rows * cols];
// Access element at (row, col):
flat[3 * cols + 7] = 99; // element at row 3, col 7Array Covariance — A Type System Subtlety
Array covariance is a Java type system feature that allows an array of a subtype to be assigned to an array of a supertype: a String[] can be assigned to an Object[] variable. This seems intuitive — since String is-an Object, a collection of Strings is-a collection of Objects. But it creates a soundness hole that the compiler cannot close and the JVM handles with a runtime check.
The problem is that covariance allows writing an incompatible type into the array. If strArray is assigned to objArray, the compiler sees objArray as Object[] and allows writing any Object into it. At runtime, the JVM knows the actual array is a String[] and throws ArrayStoreException when a non-String is written. The exception is thrown at the write site, not at the assignment site, which can be far from the actual bug.
This is the primary reason Java generics were designed to be invariant rather than covariant: List<String> cannot be assigned to List<Object>. The Java designers learned from the array covariance mistake. When working with arrays, be aware of this subtlety, and prefer generic collections (which catch type errors at compile time) for any code where type safety is critical.
Java
// ── Array covariance — allowed by the compiler ───────────────────────
String[] strings = {"hello", "world"};
Object[] objects = strings; // String[] IS-A Object[] — compiles
System.out.println(objects[0]); // "hello" — reading is fine
// ── ArrayStoreException — runtime failure, not compile error ──────────
try {
objects[0] = Integer.valueOf(42); // Compiler allows (Object write to Object[])
// JVM knows the actual array is String[] — rejects Integer
} catch (ArrayStoreException e) {
System.out.println("ArrayStoreException: " + e.getMessage());
// arrays have different types at runtime
}
// ── Why generics are invariant by design ─────────────────────────────
// List<String> strs = new ArrayList<>();
// List<Object> objs = strs; // COMPILE ERROR — List is invariant
// objs.add(42); // would break type safety if allowed
// ── Safe: reading from a covariant array ──────────────────────────────
// Reading is always safe — elements are guaranteed to be at least Object
for (Object o : objects) {
System.out.println(o.getClass().getSimpleName()); // safe to call
}
// ── Covariance with method parameters ────────────────────────────────
public static void printAll(Object[] arr) {
for (Object o : arr) System.out.println(o);
}
printAll(new String[]{"a", "b", "c"}); // String[] passed as Object[]
printAll(new Integer[]{1, 2, 3}); // Integer[] passed as Object[]
// ── Avoiding covariance pitfalls ─────────────────────────────────────
// When a method should only READ from an array, covariance is safe:
public static int indexOf(Object[] arr, Object target) {
for (int i = 0; i < arr.length; i++)
if (Objects.equals(arr[i], target)) return i;
return -1;
}
// When a method might WRITE to an array, use generics or
// be explicit about the actual type to prevent ArrayStoreExceptionArrays vs Collections — Choosing the Right Tool
The choice between arrays and collections is a practical design decision that depends on the specific requirements. Arrays have two absolute advantages: they work with primitives directly (int[], long[], double[]) without boxing overhead, and they have the smallest possible memory footprint — just the elements, no metadata or structural overhead beyond the length field. For performance-sensitive numerical code, large datasets, or interoperability with APIs that require arrays, arrays are the clear choice.
Collections — particularly ArrayList — have four advantages over arrays: variable size (add and remove elements dynamically), a rich API (sort, search, stream, filter, collect), type safety through generics (no ArrayStoreException), and better integration with the rest of the Java Collections Framework. For most application-level code where the list size is not known in advance or frequently changes, ArrayList is the right default.
The practical rule is: use arrays when you know the size at creation time and performance or primitive types matter; use ArrayList (or other collections) when size flexibility, API richness, or type safety matters more. The Arrays and Collections utility classes bridge the two worlds — Arrays.asList() wraps an array as a list view, and List.toArray() converts a list to an array.
Java
// ── Arrays: primitives without boxing ─────────────────────────────────
int[] scores = new int[1_000_000]; // 4MB, no boxing
double[] prices = new double[500_000]; // 4MB, no boxing
// ArrayList<Integer> scores — each element is an Integer object
// Each Integer is ~16 bytes on heap + 4-byte reference = ~20 bytes
// 1M elements: ~20MB vs 4MB for int[]
// ── Performance difference — primitive array vs boxed list ───────────
long sumArray = 0;
for (int score : scores) sumArray += score; // no unboxing, cache-friendly
List<Integer> boxedScores = new ArrayList<>(scores.length);
for (int s : scores) boxedScores.add(s);
long sumList = 0;
for (int s : boxedScores) sumList += s; // unboxing on each access
// ── Collections: variable size, rich API ─────────────────────────────
List<String> names = new ArrayList<>(); // grows as needed
names.add("Alice");
names.add("Bob");
names.remove("Alice");
names.sort(Comparator.naturalOrder());
Optional<String> found = names.stream()
.filter(n -> n.startsWith("B"))
.findFirst();
// Arrays have none of this — fixed size, no stream() without Arrays.stream()
// ── Bridging arrays and collections ──────────────────────────────────
// Array → fixed-size List view (backed by array — no add/remove)
String[] arr = {"Alice", "Bob", "Carol"};
List<String> fixedList = Arrays.asList(arr);
fixedList.set(0, "Anna"); // OK — modification
// fixedList.add("Dave"); // UnsupportedOperationException
// Array → independent mutable list
List<String> mutableList = new ArrayList<>(Arrays.asList(arr));
mutableList.add("Dave"); // OK — independent copy
// List → array
String[] back = mutableList.toArray(new String[0]);
// ── Decision guide ────────────────────────────────────────────────────
// Use array when:
// - Element type is primitive (int, double, etc.)
// - Size is fixed and known at creation
// - Memory footprint is critical
// - Working with APIs that require arrays (varargs, legacy)
//
// Use ArrayList when:
// - Size changes dynamically (add/remove operations)
// - Need sorting, searching, streaming convenience methods
// - Type safety of generics is important
// - Integrating with Collections FrameworkRelated Topics in Arrays
Array Basics
An array in Java is a fixed-size, ordered collection of elements of the same type stored in a contiguous block of memory. Once created, the size of an array cannot change. Arrays are the simplest and most fundamental data structure in Java — they underlie many higher-level collections and provide direct, indexed access to elements in constant time O(1). Every array in Java is an object, inherits from java.lang.Object, and can hold either primitives or references.
One-Dimensional Array
A one-dimensional array is a linear sequence of elements of the same type, accessed by a single integer index. It is the simplest array form in Java and the foundation for understanding multidimensional arrays and collection classes. One-dimensional arrays are used to store and process lists of values — student grades, product prices, daily temperatures, character sequences — any time you need to work with a fixed-size ordered collection of homogeneous data.
Two-Dimensional Array
A two-dimensional array in Java is an array of arrays — each element of the outer array is itself a one-dimensional array. It is used to represent tabular data with rows and columns: matrices, game boards, spreadsheet grids, pixel buffers, and any data that is naturally organised in two dimensions. In Java, a 2D array is declared with two sets of brackets and accessed with two indices: array[row][column].
Multidimensional Array
A multidimensional array in Java is an array with more than two dimensions — an array of arrays of arrays, and so on. While two-dimensional arrays suffice for most programming tasks, three-dimensional arrays are used for spatial data (x, y, z coordinates of a 3D grid), volumetric data (layers, rows, columns), RGB pixel buffers, and scientific computations involving tensors. Java supports arbitrary dimensionality, though arrays beyond three dimensions are rare in practice.