RizTech Academy logo
RizTech Academy
Language BasicsLesson 5 of 620 min

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. ArrayList does 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 Set answers 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 one List of 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

  1. Print an array wrongly, then correctly. System.out.println(arr), then Arrays.toString(arr). Then make a int[][] and print it with both toString and deepToString.

  2. 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.

  3. 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.

  4. Break a pair of parallel arrays. Make String[] names and int[] pricesPaise, printed together as a price list. Then call Arrays.sort(pricesPaise) and print the list again. Nothing throws. Explain to yourself what the report now claims.

  5. 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 by n, and decide what n = 6 on a four-element array should do.

  6. 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