1. Introduction to Arrays in Java
What is an Array?
In Java, an array is a data structure that allows us to store multiple values of the same type in a single variable. Instead of declaring separate variables for each value, we can group them together into a single collection.
For example:
int rollNo1 = 101;
int rollNo2 = 102;
int rollNo3 = 103;
Here, we had to create 3 different variables. If you want to store 100 student roll numbers, creating 100 variables is impractical.
Thatβs where arrays come in.
π An array can store all the roll numbers in one single variable:
int[] rollNumbers = {101, 102, 103};
Why Do We Need Arrays?
- Efficiency β Instead of declaring many variables, we can use one array.
- Structured Data Handling β Arrays allow us to loop through values, search, and manipulate them.
- Memory Management β Values are stored in contiguous memory locations, making access faster using indices.
- Scalability β Easy to manage large data sets compared to handling multiple variables.
Formal Definition
An array in Java is a container object that holds a fixed number of values of a single data type. The length of an array is established when the array is created. After creation, its length is fixed and cannot be changed.
Syntax of Arrays
There are two steps in using arrays:
- Declaration β Telling Java that you want an array of a specific type.
- Instantiation β Allocating memory for the array.
- Initialization β Assigning values to array elements.
Example 1: Separate steps
int[] numbers; // declaration
numbers = new int[5]; // instantiation (array of size 5)
numbers[0] = 10; // initialization
numbers[1] = 20;
Example 2: Combined declaration & instantiation
int[] numbers = new int[5];
Example 3: Declaration + Instantiation + Initialization
int[] numbers = {10, 20, 30, 40, 50};
Accessing Array Elements
Each element of the array is accessed by its index, starting from 0.
System.out.println(numbers[0]); // prints 10
System.out.println(numbers[2]); // prints 30
Memory Allocation of Arrays in Java
When an array is created, Java allocates a contiguous block of memory to store its elements.
- Each element is stored sequentially in memory.
- The index acts as an offset to calculate the memory address.
- Formula:
Address of arr[i] = Base Address + (i Γ Size of each element)
Diagram: 1D Array Memory Representation
Suppose we have:
int[] numbers = {10, 20, 30, 40, 50};
Memory Layout:
Index β 0 1 2 3 4
Value β 10 20 30 40 50
Address β 1000 1004 1008 1012 1016 (if int = 4 bytes)
π All values are stored sequentially (continuous block).
π Fast access: e.g., to access numbers[3], JVM directly jumps to 1000 + (3 Γ 4) = 1012.
2. Types of Arrays
- Single-Dimensional Arrays
int[] arr = {10, 20, 30};
- Multi-Dimensional Arrays
- int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
- int[][] matrix = {
- Jagged Arrays (array of arrays with different lengths)
-
int[][] jagged = new int[2][];
jagged[0] = new int[3]; // row 1 β 3 elements
jagged[1] = new int[2]; // row 2 β 2 elements
-
3. Operations on Arrays
Arrays are simple but powerful. Common array operations youβll explain in your blog are: traversing, searching, sorting, copying, and insert/delete (resize-like operations). Below each operation I show why itβs done that way, how to do it in Java, the cost (big-O), and common pitfalls.
Examples:
int[][] jagged = new int[2][];
jagged[0] = new int[3]; // row 1 β 3 elements
jagged[1] = new int[2]; // row 2 β 2 elements
3.1) Iterating
Methods
- classic for-loop β when you need the index (read/write by index):
int[] arr = {10, 20, 30, 40};
for (int i = 0; i < arr.length; i++) {
System.out.println("index=" + i + " value=" + arr[i]);
// you can update: arr[i] = arr[i] + 1;
}
- enhanced for-loop (for-each) β simpler when you only need values:
for (int value : arr) {
System.out.println(value);
}
Use for-each for readability; you can’t change the array slot with the loop variable (itβs a copy for primitives).
- while / do-while β same as for but sometimes clearer with external index:
int i = 0;
while (i < arr.length) {
System.out.println(arr[i++]);
}
- Streams (Java 8+) β concise functional style (good for mapping, filtering, aggregation):
import java.util.Arrays;
int[] arr = {1, 2, 3, 4, 5};
Arrays.stream(arr).forEach(System.out::println);
// example: sum of elements > 2
int sum = Arrays.stream(arr)
.filter(x -> x > 2)
.sum();
System.out.println(sum); // 12
Complexity
- Traversal cost: O(n) where n = arr.length.
When to use which
- Need index β classic for or while.
- Just values β enhanced for.
- Aggregation/filtering β Streams.
3.2) Searching
Two typical searches: linear search and binary search.
Linear Search
- Scans elements one-by-one.
- Works on unsorted arrays.
Code:
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i; // found index
}
return -1; // not found
}
Example:
int[] arr = {5, 3, 8, 1};
System.out.println(linearSearch(arr, 8)); // 2
System.out.println(linearSearch(arr, 7)); // -1
Complexity:
- Best-case O(1) (found at first element).
- Average & worst-case O(n).
Use when: array unsorted or small.
Binary Search
- Works only on sorted arrays.
- Repeatedly halves the search range by comparing with middle element.
How it works (visual):
Suppose sorted array [1, 3, 5, 7, 9] and target 7:
low=0, high=4
mid = (0+4)/2 = 2 -> arr[2]=5 (target > 5) β search right half
low = mid+1 = 3, high = 4
mid = (3+4)/2 = 3 -> arr[3]=7 -> found at index 3
Iterative implementation:
public static int binarySearch(int[] arr, int target) {
int low = 0, high = arr.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // avoids overflow
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // not found
}
Java built-in: Arrays.binarySearch(arr, key)
- Returns index if found.
- If not found, returns
-(insertionPoint) - 1(useful to know where it would be inserted).
Example:
int[] sorted = {1, 3, 5, 7};
System.out.println(Arrays.binarySearch(sorted, 7)); // 3
System.out.println(Arrays.binarySearch(sorted, 4)); // -3 (insertion point 2 β -2-1 = -3)
Complexity: O(log n)
Pitfall: Using binary search on an unsorted array yields undefined/incorrect results.
3.3) Sorting
Goal: arrange elements in order (ascending by default).
Built-in methods
Arrays.sort(array)β sorts primitives and object arrays.Arrays.parallelSort(array)β parallel variant for large arrays (Java 8+).
Example:
int[] nums = {5, 3, 8, 1};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums)); // [1, 3, 5, 8]
Sorting objects:
- For object arrays like
String[]orInteger[],Arrays.sortuses the objectsβ natural ordering (Comparable) or a providedComparator.
String[] names = {"Zara", "Adam", "John"};
Arrays.sort(names);
System.out.println(Arrays.toString(names)); // [Adam, John, Zara]
// with Comparator (reverse)
Arrays.sort(names, Comparator.reverseOrder());
Complexity
- Typical cost: O(n log n).
- Use
parallelSortfor large arrays when parallelism helps (multi-core).
Notes & best practices
- After sorting, you can reliably use
Arrays.binarySearch. - For objects, sorting is stable when using object sort (equal elements keep relative order); if your algorithm depends on stability, mention that in your blog.
- Sorting modifies the array in-place.
3.4) Copying arrays (and resizing)
Arrays have fixed length. To βresizeβ you create a new array and copy elements into it.
Methods to copy
System.arraycopy(src, srcPos, dest, destPos, length)β fast native copy.
int[] src = {1,2,3,4};
int[] dest = new int[6];
System.arraycopy(src, 0, dest, 0, src.length);
System.out.println(Arrays.toString(dest)); // [1,2,3,4,0,0]
Arrays.copyOf(original, newLength)β convenient; copies starting from 0:
int[] arr = {1,2,3};
int[] bigger = Arrays.copyOf(arr, 5); // [1,2,3,0,0]
int[] smaller = Arrays.copyOf(arr, 2); // [1,2]
Arrays.copyOfRange(original, from, to):
int[] arr = {0,1,2,3,4};
int[] slice = Arrays.copyOfRange(arr, 1, 4); // [1,2,3] (to is exclusive)
clone()β shallow copy for arrays:
int[] a = {1,2};
int[] b = a.clone(); // new array with same contents
Shallow vs deep copy (object arrays)
- For
String[]orInteger[](immutable), shallow copy is fine. - For arrays of mutable objects, copying copies references, not the object internals.
class Person { String name; }
Person[] p1 = new Person[] { new Person("A") };
Person[] p2 = p1.clone(); // p2[0] references same Person object as p1[0]
p2[0].name = "B"; // affects p1[0] also
- To deep-copy, you must copy each element (e.g., with a clone/copy constructor for the object).
Resizing pattern (common)
int[] arr = {1,2,3};
// need to add a new value β resize:
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = 4;
But repeated resizing with arrays is O(n) per resize and expensive. Use ArrayList when you need frequent dynamic resizing.
Complexity
- Copying: O(n) (must copy each element).
3.5) Insertions & Deletions (shifting)
Arrays are fixed-size, so add/remove require copying or shifting.
Deleting an element at index pos (shift left)
public static int[] deleteAt(int[] arr, int pos) {
if (pos < 0 || pos >= arr.length) throw new IndexOutOfBoundsException();
int[] res = new int[arr.length - 1];
System.arraycopy(arr, 0, res, 0, pos); // left chunk
System.arraycopy(arr, pos + 1, res, pos, arr.length - pos - 1); // right chunk
return res;
}
Inserting at index pos (shift right)
public static int[] insertAt(int[] arr, int pos, int value) {
int[] res = Arrays.copyOf(arr, arr.length + 1);
System.arraycopy(res, pos, res, pos + 1, arr.length - pos); // shift right
res[pos] = value;
return res;
}
Cost: insertion or deletion = O(n) due to shifting/copying.
Tip: For many inserts/removes, use ArrayList<Integer> β amortized O(1) append, O(n) insert/remove in middle, but resizing grows exponentially so fewer real copies.
3.6) Useful java.util.Arrays utilities (short guide)
Arrays.toString(arr)β human-readable print (int[]).Arrays.equals(a, b)β element-wise equality.Arrays.fill(arr, val)β fill with value.Arrays.sort(arr)β sort.Arrays.binarySearch(arr, key)β binary search.Arrays.copyOf(...),Arrays.copyOfRange(...)β copy/resize.Arrays.stream(arr)β stream operations.
Example:
int[] a = {3,1,2};
Arrays.sort(a);
System.out.println(Arrays.toString(a)); // [1,2,3]
int idx = Arrays.binarySearch(a, 2); // 1
4. Advantages and Disadvantages of Arrays
Advantages of Arrays
β
Easy to use.
β
Store multiple values of the same type.
β
Continuous memory (fast access using index).
β
Useful in low-level data handling and algorithms.
Disadvantages of Arrays
β Fixed size (cannot grow/shrink dynamically).
β Cannot store different data types in one array.
β Insertions & deletions are costly (need shifting).
β Wastage of memory if array size > required.
β Lack of built-in methods compared to collections.
5. Why Collections Framework When Arrays Already Exist?
Arrays are fundamental in Java, but they come with serious limitations:
- Fixed Size β Once you create an array, its size cannot grow or shrink. If you need a bigger array, you must create a new one and copy the old elements into it.
- Limited Utility Methods β Arrays have very few built-in operations. For example, you cannot directly add or remove elements. You need
System.arraycopy()orArrays.copyOf(), which is cumbersome. - Homogeneous Elements Only β Arrays store only one type of element. You cannot mix types (except by using
Object[], but then type-safety is lost). - Insertion & Deletion Costly β Inserting or deleting from the middle of an array requires shifting all subsequent elements, which is inefficient.
- No Built-in Data Structures β Arrays donβt provide ready-to-use structures like lists, sets, queues, maps, etc. You must build everything manually on top of arrays.
To overcome these drawbacks, Java introduced the Collections Framework, which provides dynamic, flexible, and feature-rich data structures such as ArrayList, HashSet, HashMap, LinkedList, etc. These are built on top of arrays (or linked nodes), but offer:
- Dynamic resizing (e.g.,
ArrayListgrows automatically). - Rich APIs (
add,remove,contains,sort, etc.). - Type-safety with Generics (no accidental type mismatch).
- Better readability and maintainability.
- Well-tested implementations of common data structures.
6. Arrays vs Collections Framework
| Feature | Arrays | Collections Framework |
|---|---|---|
| Size | Fixed size once created. To increase/decrease size, a new array must be created and elements copied. | Dynamic β can grow or shrink automatically (e.g., ArrayList resizes itself when elements are added/removed). |
| Type | Homogeneous only (all elements must be of the same type). Primitive arrays like int[] are supported. | Stores objects only (Generics ensure type-safety). Primitives require wrapper classes (Integer, Double). |
| Ease of Use | Limited functionality. You must write manual code for insertion, deletion, searching, etc. | Rich set of APIs (add, remove, contains, sort, iterator) make operations much easier. |
| Utility Methods | Very few methods in java.util.Arrays class (sort(), copyOf(), binarySearch(), equals(), etc.). | Hundreds of ready-to-use methods across List, Set, Map, Queue, etc. |
| Performance | Faster for primitive data types since no wrapper objects are needed. Best when working with small, fixed-size datasets. | Slight overhead due to object wrappers and dynamic resizing. But optimized implementations make them efficient in real-world scenarios. |
| Flexibility | Very low. No support for dynamic resizing, heterogeneous data, or complex structures. | Very high. Supports Lists, Sets, Maps, Queues, Stacks, etc. Can represent real-world structures more easily. |
βοΈ“Although arrays are powerful, they have fixed size and limited functionality. Thatβs why Java introduced the Collections Framework, which provides dynamic and flexible data structures such as ArrayList, HashSet, and HashMap. You can read my detailed blog on the Java Collections Framework to understand how it solves the limitations of arrays.”
Conclusion
Arrays in Java are one of the most fundamental data structures and serve as the backbone for many other data structures and algorithms. They allow you to store and manage multiple values of the same type in a contiguous block of memory, which makes access extremely fast using index-based lookup.
In this blog, we explored:
- What arrays are and why they are needed.
- Types of arrays: single-dimensional, multi-dimensional, and jagged arrays.
- Array operations such as traversal, searching, sorting, copying, insertion, and deletion.
- Advantages: simplicity, efficiency, and speed of access.
- Disadvantages: fixed size, lack of flexibility, and limited built-in methods.
- Internal working and memory allocation of arrays.
While arrays are efficient and lightweight, they fall short in real-world applications where we need dynamic resizing, flexible APIs, heterogeneous data handling, and powerful data structures. To overcome these drawbacks, Java introduced the Collections Framework, which provides dynamic and feature-rich alternatives like ArrayList, HashSet, and HashMap.