The java.util.Arrays class turns the five or six lines you would otherwise write for every array operation into one call. It sorts, fills, copies, compares, searches and streams, and it handles both primitive arrays and object arrays. Once you know which method to reach for, most array code becomes a list of one-liners instead of hand-rolled loops.
How do you print an array without wasting your time?
Calling toString on an array variable does not do what you want. The array is an object, so the inherited Object.toString prints something like [I@6d06d69c - an identity hash, not the contents. The fix is one static call: Arrays.toString(scores) returns a readable string such as [72, 45, 91, 68, 88].
import java.util.Arrays;
public class ArraysBasics {
public static void main(String[] args) {
int[] scores = { 72, 45, 91, 68, 88 };
System.out.println("before: " + Arrays.toString(scores));
Arrays.sort(scores);
System.out.println("after sort: " + Arrays.toString(scores));
System.out.println("first: " + scores[0] + ", last: " + scores[scores.length - 1]);
int[] blank = new int[5];
System.out.println("new array: " + Arrays.toString(blank));
Arrays.fill(blank, 7);
System.out.println("after fill: " + Arrays.toString(blank));
}
}before: [72, 45, 91, 68, 88]
after sort: [45, 68, 72, 88, 91]
first: 45, last: 91
new array: [0, 0, 0, 0, 0]
after fill: [7, 7, 7, 7, 7]That first line of output is the single most useful thing in this class. It also shows Arrays.sort working in place: the array you passed in is reordered, and the same variable now holds the sorted values. There is no return value to capture, which surprises people coming from functional languages.
Arrays.fill is the other one you will reach for constantly. It overwrites every element with one value, and it is the standard way to build a board, a bucket of counters or a default-filled table without a loop.
How do you copy an array without surprising the caller?
Assigning an array to a second variable copies the reference, not the data. Both names then point at one array, and any write through one is visible through the other. When you actually want an independent copy, Arrays.copyOf is the tool: give it the source and the length you want, and it allocates a new array and fills it.
import java.util.Arrays;
public class ArraysCopy {
public static void main(String[] args) {
int[] original = { 1, 2, 3, 4, 5 };
int[] grown = Arrays.copyOf(original, 7);
int[] trimmed = Arrays.copyOf(original, 2);
System.out.println("original: " + Arrays.toString(original));
System.out.println("grown: " + Arrays.toString(grown));
System.out.println("trimmed: " + Arrays.toString(trimmed));
grown[0] = 99;
System.out.println("original after editing the copy: " + Arrays.toString(original));
int[] same = original;
same[0] = 42;
System.out.println("original after editing the alias: " + Arrays.toString(original));
}
}original: [1, 2, 3, 4, 5]
grown: [1, 2, 3, 4, 5, 0, 0]
trimmed: [1, 2]
original after editing the copy: [1, 2, 3, 4, 5]
original after editing the alias: [42, 2, 3, 4, 5]Read the output carefully and you can see the two models side by side. Editing grown leaves original alone, because copyOf built a genuinely separate array. Editing same changes original, because they are the same array under two names. copyOf also handles resizing in both directions: asking for more elements than the source has pads with defaults, asking for fewer truncates.
How do you compare two arrays and search inside one?
Arrays.equals compares contents element by element, which is almost always what you meant. The == operator on two array variables only asks whether they point at the same array, so it returns false for two arrays that hold identical values. Arrays.hashCode gives you the same value for equal contents, which is why arrays used as map keys behave consistently with equals.
import java.util.Arrays;
public class ArraysCompare {
public static void main(String[] args) {
int[] a = { 4, 8, 15, 16, 23, 42 };
int[] b = { 4, 8, 15, 16, 23, 42 };
int[] c = { 4, 8, 15, 16, 23, 43 };
System.out.println("a equals b: " + Arrays.equals(a, b));
System.out.println("a equals c: " + Arrays.equals(a, c));
System.out.println("same reference: " + (a == b));
System.out.println("index of 16: " + Arrays.binarySearch(a, 16));
System.out.println("index of 99: " + Arrays.binarySearch(a, 99));
System.out.println("hash codes match: " + (Arrays.hashCode(a) == Arrays.hashCode(b)));
}
}a equals b: true
a equals c: false
same reference: false
index of 16: 3
index of 99: -7
hash codes match: trueArrays.binarySearch is the fast search, but it has a precondition you must respect: the array must already be sorted. On a sorted array it returns the index of the match, and on a miss it returns a negative number whose absolute value is the insertion point plus one. That negative result is not an error - it tells you where the value would go, which is exactly what you need to keep a sorted array sorted.
What about arrays of arrays?
For a 2D array, Arrays.toString is not enough, because it prints the inner arrays as identity strings. The method that actually works is Arrays.deepToString, which walks the whole structure and prints the leaves. It has a companion for equality: Arrays.deepEquals compares nested arrays the way equals compares flat ones.
import java.util.Arrays;
public class ArraysNested {
public static void main(String[] args) {
int[][] grid = { { 1, 2 }, { 3, 4 } };
System.out.println("plain toString: " + Arrays.toString(grid));
System.out.println("deep toString: " + Arrays.deepToString(grid));
String[][] names = { { "ana", "bo" }, { "cy" } };
System.out.println("deep names: " + Arrays.deepToString(names));
System.out.println("first row: " + Arrays.toString(names[0]));
int[] flattened = Arrays.stream(grid).flatMapToInt(row -> Arrays.stream(row)).toArray();
System.out.println("flattened: " + Arrays.toString(flattened));
System.out.println("sum: " + Arrays.stream(flattened).sum());
}
}plain toString: [[I@5ca881b5, [I@24d46ca6]
deep toString: [[1, 2], [3, 4]]
deep names: [[ana, bo], [cy]]
first row: [ana, bo]
flattened: [1, 2, 3, 4]
sum: 10The last two lines show a way to flatten a grid into a single array, which comes in handy when you want to sort everything or compute one total. Note that Arrays.stream(grid) gives you a stream of int[] rows, and flatMapToInt turns each row into its elements. The same trick works for object arrays with flatMap instead.
Which method should you reach for?
| Task | Method | Note |
|---|---|---|
| Print contents | toString | Use deepToString for nested arrays |
| Sort in place | sort | Also sort(range, from, to) for a section |
| Fill with one value | fill | Also fill(range, from, to, value) |
| Independent copy | copyOf / copyOfRange | Plain assignment copies only the reference |
| Content equality | equals / deepEquals | == compares references |
| Fast lookup | binarySearch | Array must be sorted first |
| Stream processing | Arrays.stream | Works on primitives and objects |
Two small warnings ride along with the table. Arrays.asList gives you a list view backed by the array, not a copy, so changing the list changes the array and the list is fixed in size. And every one of these methods is static - you call Arrays.sort(x), never x.sort().
The habits that pay off
- Print with
Arrays.toString; the inheritedtoStringshows an identity hash. - Never assume two equal-looking arrays are the same object; compare with
Arrays.equals. - Copy with
Arrays.copyOfbefore you mutate anything you did not create. - Sort before you binary search, and read the negative return as an insertion point.
- Use
deepToStringanddeepEqualswhenever the arrays contain arrays.
Try it yourself: create an int[] of ten random values between 1 and 100, sort it, use binarySearch to look up three values - one that exists and two that do not - and print the index for each. Then copy the array, change three values in the copy, and print both arrays to prove the original was untouched.