Arrays in Java -Complete Guide with Examples

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?

  1. Efficiency – Instead of declaring many variables, we can use one array.
  2. Structured Data Handling – Arrays allow us to loop through values, search, and manipulate them.
  3. Memory Management – Values are stored in contiguous memory locations, making access faster using indices.
  4. 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:

  1. Declaration – Telling Java that you want an array of a specific type.
  2. Instantiation – Allocating memory for the array.
  3. 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

    1. Single-Dimensional Arrays
      • int[] arr = {10, 20, 30};
    2. Multi-Dimensional Arrays
      • int[][] matrix = {
        {1, 2, 3},
        {4, 5, 6}
        };
    3. 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

    1. 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;
    }
    
    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).

    1. while / do-while β€” same as for but sometimes clearer with external index:
    int i = 0;
    while (i < arr.length) {
        System.out.println(arr[i++]);
    }
    1. 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[] or Integer[], Arrays.sort uses the objects’ natural ordering (Comparable) or a provided Comparator.
    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 parallelSort for 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

    1. 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]
    
    1. 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]
    
    1. 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)
    
    1. 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[] or Integer[] (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() or Arrays.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., ArrayList grows 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

      FeatureArraysCollections Framework
      SizeFixed 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).
      TypeHomogeneous 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 UseLimited 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 MethodsVery few methods in java.util.Arrays class (sort(), copyOf(), binarySearch(), equals(), etc.).Hundreds of ready-to-use methods across List, Set, Map, Queue, etc.
      PerformanceFaster 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.
      FlexibilityVery 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.

        Backend developer working with Java, Spring Boot, Microservices, NoSQL, and AWS. I love sharing knowledge, practical tips, and clean code practices to help others build scalable applications.

        Leave a Reply

        Your email address will not be published. Required fields are marked *