Arrays and their limitations
An array is the simplest way Java holds several values of the same type. It is also the one you will use least once module 5 arrives, and this lesson is honest about why.
You still need it. Arrays are what String[] args is, what split returns,
what every file-reading API hands back, and what List is built on top of. And
printing one does something so unhelpful that it is worth seeing immediately.
Creating one
Three forms, all common:
int[] counts = new int[5]; // five zeros
String[] names = new String[3]; // three nulls
int[] fixed = {4, 0, 12, 7, 3}; // five given values
[0, 0, 0, 0, 0]
[null, null, null]
[4, 0, 12, 7, 3]
Unlike a local variable, array elements get default values — 0 for numeric
types, false for boolean, null for anything else. The same rule as fields,
and for the same reason: the memory is allocated and zeroed in one step.
The size is fixed at creation and cannot change. new int[n] with a negative
n throws NegativeArraySizeException: -1 at runtime, not compile time, so an
array sized from user input needs checking first.
length is a field, not a method: fixed.length, with no brackets. A
String uses length() with brackets. There is no good reason for the
inconsistency; it is thirty years old and you simply learn it.
Printing one is a trap
System.out.println(fixed);
System.out.println(fixed.toString());
System.out.println(Arrays.toString(fixed));
[I@2f4948e4
[I@2f4948e4
[4, 0, 12, 7, 3]
[I@2f4948e4 is the default toString every object without its own gets: [I
means "array of int", @ separates, and the hex is an identity hash. Arrays never
got a useful toString, and they never will, because too much code would change
behaviour.
Use java.util.Arrays.toString(arr), and Arrays.deepToString(arr) for
anything nested. You will see [I@... in a log at least once in your career, and
recognising it instantly saves a confusing ten minutes.
Comparing one is the same trap again
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(Arrays.equals(a, b));
false
false
true
== compares references, as always. And a.equals(b) is also reference
comparison, because arrays inherit Object.equals and do not override it — this
is worse than the String case, where at least equals does the right thing.
Arrays.equals for contents. Arrays.deepEquals for nested arrays.
The Arrays utility methods
Import java.util.Arrays. This is the whole set you will use.
| Call | Does | Result on {4, 0, 12, 7, 3} |
|---|---|---|
Arrays.toString(a) |
Readable text | [4, 0, 12, 7, 3] |
Arrays.deepToString(a) |
Same, for nested arrays | |
Arrays.sort(a) |
Sorts in place | [0, 3, 4, 7, 12] |
Arrays.equals(a, b) |
Compares contents | |
Arrays.fill(a, 9) |
Sets every element | [9, 9, 9, 9, 9] |
Arrays.copyOf(a, 3) |
First 3, or pads with defaults | [4, 0, 12] |
Arrays.copyOf(a, 8) |
[4, 0, 12, 7, 3, 0, 0, 0] |
|
Arrays.copyOfRange(a, 1, 3) |
From, to — end exclusive | [0, 12] |
Arrays.binarySearch(a, 7) |
Index, sorted arrays only | 3 |
Arrays.stream(a).sum() |
26 |
|
Arrays.stream(a).max().getAsInt() |
12 |
|
System.arraycopy(src, 0, dst, 0, n) |
Fast bulk copy |
Two cautions. Arrays.sort modifies the array you give it and returns nothing —
copy first if you need the original order. And binarySearch on an unsorted
array returns a meaningless number rather than an error, which is a quiet way to
be wrong.
Two dimensions, and jagged ones
int[][] week = new int[2][3];
week[0][1] = 5;
System.out.println(Arrays.toString(week));
System.out.println(Arrays.deepToString(week));
[[I@3eb738bb, [I@5bda8e08]
[[0, 5, 0], [0, 0, 0]]
There is the [I@ again — Arrays.toString printed the outer array's elements,
and each element is itself an array. deepToString is what you want.
Java has no true two-dimensional array. int[][] is an array whose elements are
arrays, which means the rows can have different lengths:
int[][] jagged = {{1}, {2, 3}, {4, 5, 6}};
System.out.println(jagged[2].length);
3
Useful occasionally, and a source of ArrayIndexOutOfBoundsException when you
assume rectangularity. Always loop with row.length, not the first row's length.
One more trap: arrays are covariant
Object[] objects = new String[2];
objects[0] = 42;
java.lang.ArrayStoreException: java.lang.Integer
That compiles. A String[] is accepted where an Object[] is expected, so the
compiler allows the assignment, and the JVM has to catch the mistake at runtime.
This is a design flaw Java has acknowledged — generics, in module 4, deliberately
do not work this way, which is why List<Object> l = new ArrayList<String>()
does not compile. Remember the contrast; it makes the generics lesson much
easier.
What arrays are not for
This is the honest part.
- Anything that grows or shrinks. The size is fixed. Adding an element means
allocating a bigger array and copying.
ArrayListdoes exactly that for you, correctly, and is what you should use. - Anything you look things up in by key. That is a
Map. - Checking membership. Scanning an array is a loop; a
Setanswers in one call. - Values with meaning attached. Three parallel arrays —
names,counts,prices— indexed in lockstep is a design that breaks the first time somebody sorts one of them. One array of objects, or oneListof records, is the answer.
Use an array when the size is genuinely fixed and known, when performance
matters in a measured way, or when an API hands you one. Otherwise reach for
List. Module 5 makes this concrete.
Converting between the two:
List<String> list = Arrays.asList(array); // fixed-size view, add() throws
List<String> real = new ArrayList<>(Arrays.asList(array));
String[] back = list.toArray(new String[0]);
Arrays.asList returns a fixed-size view backed by the array, not a copy.
Calling add on it throws UnsupportedOperationException, and changing the
array changes the list. It is a common surprise; wrap it in new ArrayList<>(...)
when you want a real list.
Check your work
Why does System.out.println(arr) print [I@2f4948e4? Arrays never
override toString, so you get Object's default: a type tag ([I for int[])
and an identity hash. Use Arrays.toString, or Arrays.deepToString for nested
arrays.
Why does a.equals(b) return false for two arrays with identical
contents? Arrays do not override equals either, so it is reference
comparison. Arrays.equals(a, b) compares contents.
length or length()? length for arrays — a field, no brackets.
length() for String.
What do the elements of new String[3] contain? null. Array elements get
default values, unlike local variables.
What does Arrays.sort(a) return? Nothing. It sorts in place. Copy with
Arrays.copyOf first if you need the original.
Why does Object[] objects = new String[2]; objects[0] = 42; compile but
fail? Arrays are covariant: String[] is usable as Object[]. The compiler
cannot see the mistake, so the JVM checks at runtime and throws
ArrayStoreException. Generics are deliberately not covariant for this reason.
What does Arrays.asList(array).add("x") do? Throws
UnsupportedOperationException. It returns a fixed-size view of the array, not
a new list.
Practice 2, the running total. With Arrays.stream(a).sum() it is one line.
By hand it is:
int total = 0;
for (int value : counts) {
total += value;
}
Use the enhanced for: there is no index to get wrong.
Practice 4, the parallel-array bug. Sorting prices with Arrays.sort
reorders only that array, so names[i] and prices[i] no longer describe the
same item. Nothing throws; the report is simply wrong. The fix is not to sort
more carefully — it is to stop using parallel arrays. One array of objects keeps
the fields together, and after module 3 that is a one-line record.
Practice
-
Print an array wrongly, then correctly.
System.out.println(arr), thenArrays.toString(arr). Then make aint[][]and print it with bothtoStringanddeepToString. -
Sum and average without a stream. Given
{4, 0, 12, 7, 3}, print the total and the average to two decimal places. Watch the integer division. -
Find the largest and its position. Return both, using a single pass. Then run it on an empty array and decide what your method should do — it is a real decision, and "throw" is an acceptable answer as long as it is deliberate.
-
Break a pair of parallel arrays. Make
String[] namesandint[] pricesPaise, printed together as a price list. Then callArrays.sort(pricesPaise)and print the list again. Nothing throws. Explain to yourself what the report now claims. -
Rotate an array by one.
{1, 2, 3, 4}becomes{4, 1, 2, 3}, in place, without creating a second array of the same size. One temporary variable is enough. Then make it rotate byn, and decide whatn = 6on a four-element array should do. -
Harder — a delivery grid. A
boolean[7][4]holds four customers' deliveries across a week. Fill it with a pattern, then print a table with day numbers along the top, customer names down the side, and a tick or a dash in each cell. Then print each customer's total. This is the shape of a real report, and it is the last time you will want to write one with arrays.
Next: reading input, so your programs can finally be typed into rather than edited.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship