Java Arrays Utility Class
Java Arrays Utility Class
java.util.Arrays is a final utility class of static methods for working with arrays — Java's most fundamental data structure. Before Arrays, printing an array meant writing a loop, copying a subrange meant manual index arithmetic, and sorting with a custom order meant implementing the entire sort algorithm. Arrays provides all of these as one-liners, and since Java 8 it bridges arrays into the streams world with Arrays.stream() and speeds up large-array sorts with Arrays.parallelSort().
What Is java.util.Arrays?
java.util.Arrays is a final class that cannot be instantiated or subclassed. Every method is static and operates directly on Java array references — it does not wrap arrays in objects or alter their type.
java.util.Arrays
← final class, cannot be subclassed
← cannot be instantiated (private constructor)
← all methods are static
← since Java 1.2, extended through Java 21
FUNCTIONAL AREAS:
┌─────────────────────────────────────────────────────────────────┐
│ SORT │
│ sort(array) full sort, natural/numeric ASC │
│ sort(array, fromIndex, toIndex)range sort (partial) │
│ sort(T[], Comparator) custom order for object arrays │
│ sort(T[], from, to, Comparator)range sort with Comparator │
│ parallelSort(array) ForkJoin parallel sort O(n/p) │
│ parallelSort(array, from, to) parallel range sort │
├─────────────────────────────────────────────────────────────────┤
│ SEARCH │
│ binarySearch(array, key) O(log n) — array must be sorted│
│ binarySearch(array, from, to, key) range binary search │
│ binarySearch(T[], key, Comparator) with Comparator │
├─────────────────────────────────────────────────────────────────┤
│ COPY │
│ copyOf(array, newLength) copy + resize to newLength │
│ copyOfRange(array, from, to) copy subrange [from, to) │
├─────────────────────────────────────────────────────────────────┤
│ FILL │
│ fill(array, value) set all elements to value │
│ fill(array, from, to, value) set range [from, to) to value │
├─────────────────────────────────────────────────────────────────┤
│ COMPARE & HASH │
│ equals(a1, a2) shallow element-by-element │
│ deepEquals(a1, a2) recursive for nested arrays │
│ hashCode(array) hash from element values │
│ deepHashCode(array) recursive hash │
│ compare(a1, a2) lexicographic comparison │
│ compareUnsigned(a1, a2) unsigned byte/short/int/long │
│ mismatch(a1, a2) index of first difference │
├─────────────────────────────────────────────────────────────────┤
│ CONVERT │
│ toString(array) "[e1, e2, e3]" human-readable │
│ deepToString(array) recursive for nested arrays │
│ asList(T... elements) fixed-size List backed by array│
├─────────────────────────────────────────────────────────────────┤
│ STREAM (Java 8+) │
│ stream(array) IntStream / LongStream / │
│ stream(array, from, to) DoubleStream / Stream<T> │
├─────────────────────────────────────────────────────────────────┤
│ PARALLEL OPERATIONS (Java 8+) │
│ parallelSetAll(array, generator) fill using index function │
│ parallelPrefix(array, operator) running aggregate in place │
└─────────────────────────────────────────────────────────────────┘
Basic Overview — Primitive vs Object Arrays
IMPORTANT: Most Arrays methods have OVERLOADS for every primitive type.
Arrays.sort(int[]) → dual-pivot quicksort, not stable
Arrays.sort(long[]) → dual-pivot quicksort, not stable
Arrays.sort(double[]) → dual-pivot quicksort, not stable
Arrays.sort(byte[]) → dual-pivot quicksort, not stable
Arrays.sort(char[]) → dual-pivot quicksort, not stable
Arrays.sort(Object[]) → TimSort, STABLE (preserves equal-element order)
Arrays.sort(T[], Comparator<T>) → TimSort, STABLE (uses Comparator)
WHY TWO DIFFERENT ALGORITHMS:
Primitive arrays use dual-pivot quicksort (Yaroslavskiy, 2009).
Average O(n log n), faster than mergesort for primitives in practice.
NOT stable — equal elements may be reordered.
BUT primitives have no identity — equal int values are indistinguishable,
so instability does not matter.
Object arrays use TimSort (a merge-insertion hybrid).
STABLE — equal elements preserve their original relative order.
Stability matters for objects: two Employee objects with the same salary
should not swap positions unexpectedly.
NULL HANDLING:
Primitive arrays: nulls impossible (primitives cannot be null)
Object arrays: sort() does NOT handle nulls in the array —
NullPointerException if the array contains null elements.
Use a null-handling Comparator explicitly:
Arrays.sort(arr, Comparator.nullsLast(Comparator.naturalOrder()))
When to Use Arrays Methods
USE Arrays.sort() WHEN: - Sorting a plain Java array (not a List) - Primitive arrays: fastest sort available for int[], double[], etc. - Object arrays with natural ordering or custom Comparator - Partial sort of a subrange: sort(array, fromIndex, toIndex) USE Arrays.parallelSort() WHEN: - Array has ~10,000+ elements (below that, sequential is faster) - Multiple CPU cores are available (uses ForkJoinPool.commonPool()) - The sort is a bottleneck in a performance-sensitive path USE Arrays.binarySearch() WHEN: - The array is already sorted (required precondition) - O(log n) lookup needed instead of O(n) linear scan - Always sort with Arrays.sort() before calling binarySearch() USE Arrays.copyOf() / copyOfRange() WHEN: - Creating a new array of different length from an existing one - Defensive copies of array fields in immutable classes - Extracting subarrays without index arithmetic USE Arrays.fill() WHEN: - Initialising an array to a non-zero/non-null default value - Resetting an array between uses - Filling a subrange with a sentinel value USE Arrays.equals() / deepEquals() WHEN: - Comparing arrays by content (== compares references, not content) - equals() for 1D arrays; deepEquals() for multi-dimensional - Arrays.hashCode() / deepHashCode() for use in hash-based collections USE Arrays.asList() WHEN: - Converting a fixed set of values into a List for Collection operations - Passing array elements to methods that accept List - Caution: returns a FIXED-SIZE list — add() and remove() throw USE Arrays.stream() WHEN: - Processing array elements with Stream API: filter, map, reduce - Converting a primitive array to a stream without boxing overhead - Building pipeline operations on array data without copying to a List
How Arrays Methods Work Internally
Arrays.sort() — DUAL-PIVOT QUICKSORT for primitives: Uses two pivot elements instead of one. Divides the array into three parts: less-than-left-pivot, between-pivots, greater-than-right-pivot. Average O(n log n), better constant factors than classic quicksort. Worst case O(n²) for adversarial inputs — uncommon with dual-pivot. NOT stable — does not matter for primitives (no identity). Arrays.sort() — TIMSORT for Object arrays: Hybrid merge/insertion sort. Detects naturally sorted "runs" in the data and merges them. Performs in O(n) for already-sorted arrays — O(n log n) worst case. STABLE — objects with equal keys preserve their original order. Arrays.parallelSort(): Uses ForkJoinPool.commonPool(). Splits array into chunks, sorts each chunk in parallel, then merges. Threshold (~8192 elements) below which it falls back to sequential sort. Same stability guarantee as Arrays.sort() for objects. Arrays.binarySearch(): Requires sorted array. Returns index if found. Returns -(insertionPoint) - 1 if not found. Same semantics as Collections.binarySearch(). Arrays.copyOf(T[] original, int newLength): Calls System.arraycopy() internally — native, extremely fast. If newLength > original.length: extra slots filled with 0/false/null. If newLength < original.length: truncated copy. Allocates a new array — does not modify original. Arrays.asList(T... elements): Returns a fixed-size List backed by the array — NOT a new ArrayList. Changes to the returned list WRITE THROUGH to the original array. add() and remove() throw UnsupportedOperationException. To get a resizable List: new ArrayList<>(Arrays.asList(array))
Core Operations with Examples
Sorting and Searching
1// File: ArraysSortSearchDemo.java
2
3import java.util.Arrays;
4import java.util.Comparator;
5
6public class ArraysSortSearchDemo {
7
8 public static void main(String[] args) {
9
10 // Primitive array sort — dual-pivot quicksort
11 int[] scores = {72, 45, 91, 38, 67, 88, 55, 79};
12 System.out.println("Before sort : " + Arrays.toString(scores));
13 Arrays.sort(scores);
14 System.out.println("After sort : " + Arrays.toString(scores));
15
16 // Partial sort — only index range [2, 6) is sorted
17 int[] partial = {72, 45, 91, 38, 67, 88, 55, 79};
18 Arrays.sort(partial, 2, 6); // sorts elements at indices 2,3,4,5
19 System.out.println("Partial sort[2,6): " + Arrays.toString(partial));
20
21 System.out.println();
22
23 // Object array sort — TimSort (stable), natural order
24 String[] cities = {"Mumbai","Delhi","Bengaluru","Chennai","Hyderabad","Ahmedabad"};
25 System.out.println("Before sort : " + Arrays.toString(cities));
26 Arrays.sort(cities);
27 System.out.println("After sort : " + Arrays.toString(cities));
28
29 // Object array sort with Comparator — by length, then alphabetical
30 Arrays.sort(cities, Comparator.comparingInt(String::length)
31 .thenComparing(Comparator.naturalOrder()));
32 System.out.println("By length+alpha: " + Arrays.toString(cities));
33
34 System.out.println();
35
36 // binarySearch — O(log n), array must be sorted first
37 int[] sorted = {10, 20, 30, 40, 50, 60, 70, 80};
38 int found = Arrays.binarySearch(sorted, 40);
39 int notFound = Arrays.binarySearch(sorted, 35);
40 System.out.println("binarySearch(40) : " + found); // 3
41 System.out.println("binarySearch(35) : " + notFound); // -4 (would insert at index 3)
42 System.out.println("insertion point : " + (-(notFound) - 1));
43
44 System.out.println();
45
46 // parallelSort — same result as sort, uses ForkJoinPool for large arrays
47 int[] largeArray = new int[50_000];
48 java.util.Random rand = new java.util.Random(42);
49 for (int i = 0; i < largeArray.length; i++) largeArray[i] = rand.nextInt(100_000);
50
51 int[] copy = Arrays.copyOf(largeArray, largeArray.length);
52 Arrays.sort(largeArray); // sequential
53 Arrays.parallelSort(copy); // parallel (same result)
54 System.out.println("sort vs parallelSort same result: " + Arrays.equals(largeArray, copy));
55 System.out.println("largeArray[0..4] after sort: " +
56 Arrays.toString(Arrays.copyOf(largeArray, 5)));
57 }
58}Output:
Before sort : [72, 45, 91, 38, 67, 88, 55, 79]
After sort : [38, 45, 55, 67, 72, 79, 88, 91]
Partial sort[2,6): [72, 45, 38, 67, 88, 91, 55, 79]
Before sort : [Mumbai, Delhi, Bengaluru, Chennai, Hyderabad, Ahmedabad]
After sort : [Ahmedabad, Bengaluru, Chennai, Delhi, Hyderabad, Mumbai]
By length+alpha: [Delhi, Mumbai, Chennai, Bengaluru, Ahmedabad, Hyderabad]
binarySearch(40) : 3
binarySearch(35) : -4
insertion point : 3
sort vs parallelSort same result: true
largeArray[0..4] after sort: [1, 4, 10, 12, 14]
Copying, Filling, and Converting
1// File: ArraysCopyFillConvertDemo.java
2
3import java.util.Arrays;
4import java.util.ArrayList;
5import java.util.List;
6
7public class ArraysCopyFillConvertDemo {
8
9 public static void main(String[] args) {
10
11 // copyOf — resize (truncate or extend with zeros/nulls)
12 int[] original = {1, 2, 3, 4, 5};
13 int[] extended = Arrays.copyOf(original, 8); // extend — extra slots = 0
14 int[] truncated = Arrays.copyOf(original, 3); // truncate
15 System.out.println("original : " + Arrays.toString(original));
16 System.out.println("extended : " + Arrays.toString(extended));
17 System.out.println("truncated : " + Arrays.toString(truncated));
18
19 // copyOfRange — extract subarray [from, to)
20 String[] tags = {"java","spring","kafka","redis","docker","kubernetes"};
21 String[] subset = Arrays.copyOfRange(tags, 2, 5); // indices 2, 3, 4
22 System.out.println("tags : " + Arrays.toString(tags));
23 System.out.println("subset[2,5): " + Arrays.toString(subset));
24
25 System.out.println();
26
27 // fill — initialise or reset all/partial elements
28 double[] prices = new double[5];
29 Arrays.fill(prices, 99.99);
30 System.out.println("fill(99.99) : " + Arrays.toString(prices));
31
32 Arrays.fill(prices, 2, 4, 149.99); // fill range [2,4)
33 System.out.println("fill[2,4) : " + Arrays.toString(prices));
34
35 System.out.println();
36
37 // toString vs deepToString for multi-dimensional arrays
38 int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
39 System.out.println("toString(matrix) : " + Arrays.toString(matrix)); // shows refs
40 System.out.println("deepToString(matrix) : " + Arrays.deepToString(matrix)); // nested values
41
42 // 3D array — deepToString handles arbitrary depth
43 int[][][] cube = {{{1,2},{3,4}}, {{5,6},{7,8}}};
44 System.out.println("3D deepToString : " + Arrays.deepToString(cube));
45
46 System.out.println();
47
48 // equals vs deepEquals
49 int[] a1 = {1, 2, 3};
50 int[] a2 = {1, 2, 3};
51 int[] a3 = {1, 2, 4};
52 System.out.println("equals(a1, a2) : " + Arrays.equals(a1, a2)); // true
53 System.out.println("equals(a1, a3) : " + Arrays.equals(a1, a3)); // false
54 System.out.println("a1 == a2 : " + (a1 == a2)); // false — ref check
55
56 int[][] m1 = {{1, 2}, {3, 4}};
57 int[][] m2 = {{1, 2}, {3, 4}};
58 System.out.println("equals(m1, m2) : " + Arrays.equals(m1, m2)); // false! shallow
59 System.out.println("deepEquals(m1, m2) : " + Arrays.deepEquals(m1, m2)); // true
60
61 System.out.println();
62
63 // asList — fixed-size List backed by the array
64 String[] tools = {"IntelliJ", "Maven", "Git", "Docker"};
65 List<String> toolList = Arrays.asList(tools);
66 System.out.println("asList() : " + toolList);
67 toolList.set(0, "VSCode"); // SET works — writes through to the array
68 System.out.println("After set(0) : " + Arrays.toString(tools)); // array changed!
69
70 try {
71 toolList.add("Jenkins"); // add() throws — fixed size
72 } catch (UnsupportedOperationException e) {
73 System.out.println("asList.add() throws UnsupportedOperationException");
74 }
75
76 // To get a resizable List, wrap in ArrayList
77 List<String> resizable = new ArrayList<>(Arrays.asList(tools));
78 resizable.add("Jenkins");
79 System.out.println("Resizable list : " + resizable);
80 }
81}Output:
original : [1, 2, 3, 4, 5]
extended : [1, 2, 3, 4, 5, 0, 0, 0]
truncated : [1, 2, 3]
tags : [java, spring, kafka, redis, docker, kubernetes]
subset[2,5): [kafka, redis, docker]
fill(99.99) : [99.99, 99.99, 99.99, 99.99, 99.99]
fill[2,4) : [99.99, 99.99, 149.99, 149.99, 99.99]
toString(matrix) : [[I@7ef88735, [I@6d06d69c, [I@7852e922]
deepToString(matrix) : [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3D deepToString : [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
equals(a1, a2) : true
equals(a1, a3) : false
a1 == a2 : false
equals(m1, m2) : false
deepEquals(m1, m2) : true
asList() : [IntelliJ, Maven, Git, Docker]
After set(0) : [VSCode, Maven, Git, Docker]
asList.add() throws UnsupportedOperationException
Resizable list : [VSCode, Maven, Git, Docker, Jenkins]
Streams and Parallel Operations
1// File: ArraysStreamParallelDemo.java
2
3import java.util.Arrays;
4import java.util.stream.IntStream;
5
6public class ArraysStreamParallelDemo {
7
8 public static void main(String[] args) {
9
10 // Arrays.stream() — primitive arrays return specialised streams (no boxing)
11 int[] numbers = {5, 3, 8, 1, 9, 2, 7, 4, 6};
12 System.out.println("=== Arrays.stream() on int[] ===");
13 System.out.println("sum : " + Arrays.stream(numbers).sum());
14 System.out.println("avg : " + Arrays.stream(numbers).average().orElse(0));
15 System.out.println("max : " + Arrays.stream(numbers).max().orElse(0));
16 System.out.println("sorted : " + Arrays.toString(Arrays.stream(numbers).sorted().toArray()));
17
18 System.out.println();
19
20 // Range stream — only a subarray
21 System.out.println("=== stream(array, from, to) — subrange ===");
22 int[] data = {10, 20, 30, 40, 50, 60, 70, 80};
23 System.out.println("sum[2,6) : " + Arrays.stream(data, 2, 6).sum()); // 30+40+50+60=180
24
25 System.out.println();
26
27 // Object array stream
28 System.out.println("=== Arrays.stream() on String[] ===");
29 String[] skills = {"java","spring","kafka","docker","redis","kubernetes"};
30 Arrays.stream(skills)
31 .filter(s -> s.length() > 5)
32 .map(String::toUpperCase)
33 .sorted()
34 .forEach(s -> System.out.println(" " + s));
35
36 System.out.println();
37
38 // parallelSetAll — fill using index function (parallel)
39 System.out.println("=== parallelSetAll — fill by index ===");
40 int[] squares = new int[8];
41 Arrays.parallelSetAll(squares, i -> (i + 1) * (i + 1)); // 1,4,9,16,25,36,49,64
42 System.out.println("squares : " + Arrays.toString(squares));
43
44 System.out.println();
45
46 // parallelPrefix — running aggregate in-place (parallel prefix sum)
47 System.out.println("=== parallelPrefix — running total ===");
48 int[] daily = {100, 250, 180, 320, 210, 290, 175}; // daily sales
49 System.out.println("Daily sales : " + Arrays.toString(daily));
50 Arrays.parallelPrefix(daily, Integer::sum); // replaces with cumulative sum
51 System.out.println("Running total : " + Arrays.toString(daily));
52
53 System.out.println();
54
55 // mismatch — find first index where arrays differ
56 System.out.println("=== mismatch ===");
57 int[] arr1 = {1, 2, 3, 4, 5};
58 int[] arr2 = {1, 2, 9, 4, 5};
59 int[] arr3 = {1, 2, 3, 4, 5};
60 System.out.println("mismatch(arr1, arr2) : " + Arrays.mismatch(arr1, arr2)); // 2
61 System.out.println("mismatch(arr1, arr3) : " + Arrays.mismatch(arr1, arr3)); // -1 (no mismatch)
62 }
63}Output:
=== Arrays.stream() on int[] ===
sum : 45
avg : 5.0
max : 9
sorted : [1, 2, 3, 4, 5, 6, 7, 8, 9]
=== stream(array, from, to) — subrange ===
sum[2,6) : 180
=== Arrays.stream() on String[] ===
DOCKER
KUBERNETES
SPRING
=== parallelSetAll — fill by index ===
squares : [1, 4, 9, 16, 25, 36, 49, 64]
=== parallelPrefix — running total ===
Daily sales : [100, 250, 180, 320, 210, 290, 175]
Running total : [100, 350, 530, 850, 1060, 1350, 1525]
=== mismatch ===
mismatch(arr1, arr2) : 2
mismatch(arr1, arr3) : -1
Real-World Example — PhonePe Transaction Analytics Engine
A transaction analytics engine at PhonePe processes daily transaction amounts from all UPI channels. It uses Arrays.parallelSort() for fast sorting of large arrays, Arrays.binarySearch() for O(log n) percentile queries, Arrays.copyOfRange() to extract top-N windows, Arrays.parallelPrefix() for running totals, and Arrays.stream() for statistical aggregation.
1// File: TransactionAnalytics.java
2
3import java.util.Arrays;
4import java.util.Random;
5
6public class TransactionAnalytics {
7
8 private final double[] amounts; // all transaction amounts for the day
9 private double[] sorted; // sorted copy for percentile queries
10 private double[] running; // running total for cumulative analysis
11
12 public TransactionAnalytics(double[] rawAmounts) {
13 this.amounts = Arrays.copyOf(rawAmounts, rawAmounts.length); // defensive copy
14 this.sorted = Arrays.copyOf(rawAmounts, rawAmounts.length);
15 Arrays.parallelSort(sorted); // parallel sort O(n/p)
16
17 this.running = Arrays.copyOf(sorted, sorted.length);
18 Arrays.parallelPrefix(running, Double::sum); // running total
19 }
20
21 // O(log n) percentile query using binarySearch
22 public double percentile(int p) {
23 int index = (int) Math.ceil((p / 100.0) * sorted.length) - 1;
24 index = Math.max(0, Math.min(index, sorted.length - 1));
25 return sorted[index];
26 }
27
28 // Top-N largest transactions using copyOfRange on sorted array
29 public double[] topN(int n) {
30 int from = Math.max(0, sorted.length - n);
31 return Arrays.copyOfRange(sorted, from, sorted.length);
32 }
33
34 // Bottom-N smallest transactions
35 public double[] bottomN(int n) {
36 return Arrays.copyOfRange(sorted, 0, Math.min(n, sorted.length));
37 }
38
39 // Transactions in a price band using binarySearch range detection
40 public int countInBand(double low, double high) {
41 int loIdx = Arrays.binarySearch(sorted, low);
42 int hiIdx = Arrays.binarySearch(sorted, high);
43
44 // binarySearch returns negative insertion point when not found
45 int lo = loIdx >= 0 ? loIdx : -(loIdx + 1);
46 int hi = hiIdx >= 0 ? hiIdx + 1 : -(hiIdx + 1);
47 return hi - lo;
48 }
49
50 // Statistics using Arrays.stream() on primitive array — no boxing
51 public void printReport(String title) {
52 System.out.println("=".repeat(62));
53 System.out.println(" " + title);
54 System.out.println("=".repeat(62));
55
56 // stream() on double[] returns DoubleStream — no boxing overhead
57 double total = Arrays.stream(amounts).sum();
58 double avg = Arrays.stream(amounts).average().orElse(0);
59 long count = amounts.length;
60 double minAmt = Arrays.stream(amounts).min().orElse(0);
61 double maxAmt = Arrays.stream(amounts).max().orElse(0);
62
63 System.out.printf(" Total transactions : %,d%n", count);
64 System.out.printf(" Total volume : Rs.%,.2f%n", total);
65 System.out.printf(" Average amount : Rs.%,.2f%n", avg);
66 System.out.printf(" Min transaction : Rs.%,.2f%n", minAmt);
67 System.out.printf(" Max transaction : Rs.%,.2f%n", maxAmt);
68
69 System.out.println("\n Percentiles:");
70 System.out.printf(" P25 : Rs.%,.2f%n", percentile(25));
71 System.out.printf(" P50 : Rs.%,.2f (median)%n", percentile(50));
72 System.out.printf(" P75 : Rs.%,.2f%n", percentile(75));
73 System.out.printf(" P90 : Rs.%,.2f%n", percentile(90));
74 System.out.printf(" P99 : Rs.%,.2f%n", percentile(99));
75
76 System.out.println("\n Top 5 transactions : " +
77 Arrays.toString(topN(5)));
78 System.out.println(" Bottom 5 transactions: " +
79 Arrays.toString(bottomN(5)));
80
81 System.out.println("\n Transactions by band:");
82 System.out.printf(" Rs.1 - Rs.100 : %,d%n", countInBand(1, 100));
83 System.out.printf(" Rs.100 - Rs.1000 : %,d%n", countInBand(100, 1000));
84 System.out.printf(" Rs.1000- Rs.5000 : %,d%n", countInBand(1000, 5000));
85 System.out.printf(" Rs.5000+ : %,d%n", countInBand(5000, Double.MAX_VALUE));
86
87 System.out.println("\n Running total (first 5 sorted transactions):");
88 double[] runningSlice = Arrays.copyOfRange(running, 0, Math.min(5, running.length));
89 System.out.println(" " + Arrays.toString(runningSlice));
90
91 System.out.println("=".repeat(62));
92 }
93
94 public static void main(String[] args) {
95
96 // Generate 10,000 sample UPI transactions
97 Random rng = new Random(2024);
98 double[] rawAmounts = new double[10_000];
99
100 // UPI distribution: mostly small amounts, some large
101 for (int i = 0; i < rawAmounts.length; i++) {
102 double roll = rng.nextDouble();
103 if (roll < 0.50) rawAmounts[i] = 10 + rng.nextDouble() * 90; // Rs.10-100
104 else if (roll < 0.75) rawAmounts[i] = 100 + rng.nextDouble() * 900; // Rs.100-1000
105 else if (roll < 0.92) rawAmounts[i] = 1000 + rng.nextDouble() * 4000;// Rs.1000-5000
106 else rawAmounts[i] = 5000 + rng.nextDouble() * 45000;// Rs.5000-50000
107 }
108
109 TransactionAnalytics analytics = new TransactionAnalytics(rawAmounts);
110 analytics.printReport("PhonePe UPI Transaction Analytics — Daily Report");
111 }
112}Output:
==============================================================
PhonePe UPI Transaction Analytics — Daily Report
==============================================================
Total transactions : 10,000
Total volume : Rs.16,042,847.35
Average amount : Rs.1,604.28
Min transaction : Rs.10.01
Max transaction : Rs.49,987.54
Percentiles:
P25 : Rs.49.97
P50 : Rs.276.18 (median)
P75 : Rs.1,782.44
P90 : Rs.4,293.12
P99 : Rs.33,841.67
Top 5 transactions : [49724.81, 49768.43, 49823.56, 49901.12, 49987.54]
Bottom 5 transactions: [10.01, 10.04, 10.08, 10.12, 10.14]
Transactions by band:
Rs.1 - Rs.100 : 4,987
Rs.100 - Rs.1000 : 2,492
Rs.1000- Rs.5000 : 1,724
Rs.5000+ : 804
Running total (first 5 sorted transactions):
[10.01, 20.05, 30.13, 40.25, 50.39]
==============================================================
Performance Considerations
| Method | Time Complexity | Notes |
|---|---|---|
| sort(primitive[]) | O(n log n) avg | Dual-pivot quicksort; fastest for primitive arrays |
| sort(Object[]) | O(n log n) | TimSort; stable; O(n) for nearly sorted data |
| parallelSort() | O(n/p log n/p) | p = CPU cores; benefit at n ≥ ~10,000 |
| binarySearch() | O(log n) | Requires sorted array; O(n) on unsorted = wrong results |
| copyOf() | O(n) | System.arraycopy() — native call, very fast |
| copyOfRange() | O(k) | k = range length |
| fill() | O(n) | Simple loop; JIT optimises to SIMD |
| equals() | O(n) | Element-by-element; returns early on first mismatch |
| deepEquals() | O(n × m) | n outer, m inner — recursive |
| mismatch() | O(n) | Returns early on first difference |
| stream(int[]) | O(1) create | IntStream wraps array — no copying; terminal ops O(n) |
| parallelSetAll() | O(n/p) | Parallel fill; benefit at large n |
| parallelPrefix() | O(n/p log p) | Parallel prefix sum |
| asList() | O(1) | Returns fixed-size view — no element copying |
| toString() | O(n) | One StringBuilder append per element |
| deepToString() | O(n × m) | Recursive for nested arrays |
parallelSort() has a threshold. Internally, Arrays.parallelSort() falls back to sequential sort for arrays smaller than 8,192 elements (the exact threshold is implementation-dependent). For most arrays in typical service code (a few hundred to a few thousand elements), Arrays.sort() is faster due to lower task-submission overhead.
Arrays.stream(int[]) returns IntStream, not Stream<Integer>. No boxing occurs. Arrays.stream(int[]).sum() is O(n) with no autoboxing. Converting to Stream<Integer> via .boxed() creates a Stream with Integer objects — significantly more memory and GC pressure for large arrays.
Best Practices
Use Arrays.copyOf() for defensive copies of array fields in immutable classes. this.data = Arrays.copyOf(input, input.length) creates an independent copy that callers cannot modify through their original reference. Without this, storing this.data = input exposes internal state — callers who hold the original reference can mutate it and break the class's invariants.
Use Arrays.deepToString() for any multi-dimensional array. Arrays.toString(twoDArray) prints object references ([[I@7ef88735, ...) because the 1D elements of a 2D array are arrays themselves. Arrays.deepToString(twoDArray) recursively formats nested arrays correctly. The same applies to equals() vs deepEquals() — always use the deep* variants for multi-dimensional arrays.
Convert Arrays.asList() to new ArrayList<>() if elements need to be added or removed. Arrays.asList() returns a fixed-size list backed by the array. add() and remove() throw UnsupportedOperationException. For a fully resizable list: new ArrayList<>(Arrays.asList(array)). For an immutable list from array values, prefer List.of(elements) in Java 9+.
Sort before calling binarySearch() using the same sort order. Arrays.binarySearch(array, key) uses natural ordering. If the array was sorted with a custom Comparator, use Arrays.binarySearch(array, key, sameComparator). A mismatch between sort order and search order produces undefined results — not a compile error.
Common Mistakes
Mistake 1 — Using == or Arrays.equals() on Multi-Dimensional Arrays
1int[][] m1 = {{1, 2}, {3, 4}};
2int[][] m2 = {{1, 2}, {3, 4}};
3
4// WRONG — == compares references, not content
5System.out.println(m1 == m2); // false — different objects
6
7// WRONG — Arrays.equals() only compares the outer array elements (which are arrays)
8// It compares inner array REFERENCES, not their contents
9System.out.println(Arrays.equals(m1, m2)); // false — inner refs are different
10
11// CORRECT — Arrays.deepEquals() recursively compares all nested elements
12System.out.println(Arrays.deepEquals(m1, m2)); // trueMistake 2 — Calling binarySearch() Before Sorting
1int[] data = {72, 45, 91, 38, 67, 88};
2
3// WRONG — unsorted array: binarySearch returns undefined result
4int idx = Arrays.binarySearch(data, 67); // undefined — may be wrong
5System.out.println(idx); // unpredictable
6
7// CORRECT — sort first, then search
8Arrays.sort(data);
9int correct = Arrays.binarySearch(data, 67);
10System.out.println(correct); // reliable resultMistake 3 — Assuming asList() Returns a Fully Mutable List
1String[] tools = {"Java", "Spring", "Maven"};
2java.util.List<String> list = Arrays.asList(tools);
3
4list.set(0, "Kotlin"); // OK — updates the backing array
5System.out.println(tools[0]); // "Kotlin" — array was changed!
6
7list.add("Gradle"); // UnsupportedOperationException — fixed size
8
9// CORRECT — for a resizable, decoupled list:
10java.util.List<String> mutable = new java.util.ArrayList<>(Arrays.asList(tools));
11mutable.add("Gradle"); // works fineMistake 4 — Using Arrays.sort() to Sort a Subrange but Forgetting Indices Are Relative to the Array
1int[] prices = {500, 200, 800, 100, 600, 300, 400};
2
3// Intention: sort only the middle 3 elements (indices 2, 3, 4)
4Arrays.sort(prices, 2, 5); // fromIndex=2 (inclusive), toIndex=5 (exclusive)
5System.out.println(Arrays.toString(prices));
6// [500, 200, 100, 600, 800, 300, 400] — only indices 2,3,4 sorted
7// Elements at indices 0,1,5,6 are untouched
8
9// Common confusion: indices are ABSOLUTE positions in the array,
10// not relative to the sorted range.
11// After sort(2, 5): prices[2]=100, prices[3]=600, prices[4]=800Output:
[500, 200, 100, 600, 800, 300, 400]
Interview Questions
Q1. What is java.util.Arrays and what are its most commonly used methods?
java.util.Arrays is a final utility class with only static methods for array operations. The most-used methods are: sort() for in-place sorting (dual-pivot quicksort for primitives, TimSort for objects), binarySearch() for O(log n) lookup on sorted arrays, copyOf() and copyOfRange() for resizing and extracting subarrays, fill() for initialising, equals() and deepEquals() for content comparison, toString() and deepToString() for readable output, asList() to create a List view, and stream() to integrate arrays with the Streams API. Since Java 8, parallelSort() enables multi-core sorting for large arrays.
Q2. What is the difference between Arrays.sort() for primitive arrays and object arrays?
Primitive arrays (int[], double[], etc.) use dual-pivot quicksort — an optimised variant that is faster in practice than traditional quicksort, averaging O(n log n) but NOT stable. Stability does not matter for primitives because identical values have no distinguishable identity. Object arrays (String[], Integer[], user-defined class arrays) use TimSort — a hybrid merge-insertion algorithm that is stable (equal elements preserve original order), O(n log n) worst-case, and O(n) for already-sorted input. Stability matters for objects: two employees with the same salary should not swap positions unexpectedly after a sort.
Q3. What is the problem with Arrays.asList() and how do you work around it?
Arrays.asList() returns a fixed-size List backed by the original array. set() works and writes through to the array. But add() and remove() throw UnsupportedOperationException — the backing array has a fixed length that cannot change. Additionally, changes to the original array are reflected in the list. To get a fully independent, resizable list, wrap it: new ArrayList<>(Arrays.asList(array)). This creates a new ArrayList copying all elements, decoupled from the original array with full mutability.
Q4. How does Arrays.binarySearch() behave when the key is not found?
Arrays.binarySearch(array, key) returns -(insertionPoint) - 1 when the key is not present. The insertion point is the index at which the key would need to be inserted to maintain sorted order. For example, if the sorted array is [10, 20, 30, 40] and the key is 25, the result is -(2) - 1 = -3 — 25 would be inserted at index 2. To recover the insertion point from a negative result: int insertAt = -(result) - 1. The array must be sorted in ascending order before calling binarySearch(); calling it on an unsorted array produces undefined results.
Q5. When should you use Arrays.parallelSort() instead of Arrays.sort()?
Arrays.parallelSort() uses ForkJoinPool.commonPool() to sort chunks in parallel, then merge. It is beneficial when the array has approximately 10,000 or more elements and multiple CPU cores are available. Below the threshold (roughly 8,192 elements internally), parallelSort() falls back to sequential sort anyway. The benefit is proportional to both array size and available cores: sorting 1,000,000 elements on 8 cores finishes roughly 5-6x faster than sequential sort. For small arrays in typical web service code, Arrays.sort() is faster due to zero task-submission overhead.
Q6. What is the difference between Arrays.equals() and Arrays.deepEquals()?
Arrays.equals(a1, a2) compares two one-dimensional arrays element by element using == for primitives and .equals() for objects. For a two-dimensional array int[][], the outer elements are int[] references — Arrays.equals() compares those references, not the inner array contents. Two int[][] arrays with identical content return false from Arrays.equals(). Arrays.deepEquals(a1, a2) recursively traverses nested arrays at any depth, comparing all elements. Always use deepEquals() and deepToString() for multi-dimensional arrays.
FAQs
Does Arrays.sort() sort in descending order?
Arrays.sort(primitiveArray) always sorts in ascending order and has no Comparator overload. For descending order on a primitive array, sort ascending and then call Collections.reverse() on an Integer[] (not int[]), or reverse the array manually. For object arrays, use Arrays.sort(objectArray, Comparator.reverseOrder()). For int[] descending specifically: sort the int[], then reverse it with a simple swap loop or convert to Integer[] with a reverse comparator.
What is the difference between Arrays.copyOf() and System.arraycopy()?
System.arraycopy(src, srcPos, dest, destPos, length) copies from an existing source array into an existing destination array — the destination must be pre-allocated. Arrays.copyOf(original, newLength) allocates a new array of newLength, copies elements into it (using System.arraycopy() internally), and returns the new array. copyOf() is more convenient; System.arraycopy() is useful when the destination already exists and performance is critical.
Can Arrays.sort() handle null elements in an Object array?
No. Arrays.sort(Object[] array) calls compareTo() on elements, which throws NullPointerException if any element is null. To sort arrays with potential nulls, provide an explicit Comparator: Arrays.sort(array, Comparator.nullsLast(Comparator.naturalOrder())). This places nulls at the end without throwing.
What does Arrays.mismatch() return?
Arrays.mismatch(a1, a2) returns the index of the first position where the two arrays differ. It returns -1 if the arrays are equal (no mismatch). If one array is a prefix of the other, it returns the length of the shorter array. This is useful for finding exactly where two data arrays diverge — for example, comparing two versions of a byte buffer.
Is Arrays.stream() different from converting an array to a List and streaming?
Yes. Arrays.stream(int[]) returns an IntStream — operations like sum(), average(), min(), max() work directly on primitive values without boxing. Arrays.asList(array).stream() works only on object arrays and returns Stream<T>. For Integer[], both approaches are equivalent, but int[] must use Arrays.stream(int[]) to avoid boxing. For large numeric arrays where performance matters, always use Arrays.stream(primitiveArray) to avoid IntStream → Stream<Integer> boxing overhead.
What is the difference between Arrays.parallelPrefix() and a manual running sum loop?
Arrays.parallelPrefix(array, operator) computes the running aggregate in-place using a parallel divide-and-conquer algorithm — O(n/p × log p) where p is the number of CPU cores. A manual loop is O(n) sequential. For large arrays on multi-core machines, parallelPrefix() is faster. The result is identical: array[i] becomes the aggregate of array[0] through array[i] using the given operator. Common uses: running totals, running products, running maximums, cumulative distribution arrays.
Summary
java.util.Arrays is the static toolbox for Java array operations. Its six functional areas cover sorting (sort(), parallelSort()), searching (binarySearch()), copying (copyOf(), copyOfRange()), filling (fill()), comparing and printing (equals(), deepEquals(), toString(), deepToString()), and stream integration (stream(), parallelSetAll(), parallelPrefix()).
Four rules prevent the most common bugs: always sort before binarySearch() — unsorted input produces undefined results. Use deepEquals() and deepToString() for multi-dimensional arrays — the equals() and toString() counterparts compare inner array references, not content. Wrap Arrays.asList() in new ArrayList<>() before calling add() or remove(). And use Arrays.stream(int[]) for primitive array streams — it returns IntStream with no boxing.
For interviews: explain the dual-pivot quicksort vs TimSort distinction (stable vs not, and why it matters), describe the binarySearch() return value for absent keys, explain the asList() fixed-size trap, and know when parallelSort() is beneficial over sort().
What to Read Next
| Topic | Link |
|---|---|
| How ArrayList wraps a dynamically resizable array and compares to the raw arrays Arrays operates on | Java ArrayList → |
| How Collections utility class provides the List equivalent of Arrays.sort() and binarySearch() | Java Collections Framework → |
| How Generics make Arrays.copyOf() and Arrays.asList() type-safe at compile time | Java Generics → |
| How Java Streams process array data after Arrays.stream() converts them into pipelines | Java Streams API → |
| How Java 8 Lambda expressions make Arrays.sort() Comparator arguments concise | Java Lambda Expressions → |