Skip to content

Arrays in Java – An Illustrated Guide

Hi there! Arrays can seem tricky when you‘re first learning Java, but I‘m here to walk you through everything you need to know about using arrays in your programs. Grab a coffee and let‘s dive in!

Arrays allow us to store multiple values together in a single variable. You can think of them like a shelf that holds a row of books – each "slot" in the shelf can hold one book, just like each element in an array holds a value.

Java arrays have been core to the language since the very beginning in 1996. They provide a simple, efficient way to organize data that‘s crucial for almost every application.

Over the past 20+ years, Java arrays have evolved to work with modern features like generics and functional programming. But the core concepts remain the same – it‘s still those familiar square brackets for declaring arrays:

String[] books;

Let‘s start from the beginning and build up an in-depth understanding of arrays in Java.

Declaring Arrays

To declare an array variable, you specify the type of elements it will hold, followed by square brackets:

int[] numbers; // array of ints
String[] names; // array of Strings
Book[] books; // array of Book objects

This simply declares a variable that can hold an array. It doesn‘t actually create the array yet!

To do that, we need to construct a new array instance:

int[] numbers = new int[5]; // array of 5 ints

The new keyword allocates memory for the array. Inside the brackets we pass the size, in this case 5 elements.

When declared like this, the array elements will automatically be initialized to default values – 0 for ints, null for object references.

We can also combine the declaration and construction in one statement:

int[] numbers = new int[5];

And that‘s it! You‘ve created your first array in Java.

Accessing Elements

Once you‘ve created an array, you can store data by assigning values to each element:

numbers[0] = 1; 
numbers[1] = 2;
numbers[2] = 3; 
// etc...

The index expression inside [...] specifies the position of the element you want.

Indexes start at 0 for the first element, 1 for the second, and so on:

Array indexing diagram

To access a value, just use the index again:

int first = numbers[0];
int second = numbers[1];

This makes arrays incredibly fast and efficient – you can instantly access any element without having to search/scan through other items.

Let‘s look at a more real-world example. Say we have an array holding prices for a grocery store:

float[] prices = new float[5];

prices[0] = 2.40f; // apples 
prices[1] = 3.99f; // milk
prices[2] = 0.99f; // oats  
prices[3] = 1.59f; // soda
prices[4] = 0.77f; // eggs

We can quickly look up the price of any item, without having to know details about the other prices:

float sodaPrice = prices[3]; // 1.59

This makes arrays perfect for organized, indexed data sets like matrices, tables, ledgers, etc.

Array Lengths

In the previous examples, we explicitly passed the array length when constructing it with new:

int[] nums = new int[5]; // array of length 5

But how can we dynamically get or set the length?

For this, arrays provide a handy .length property:

int length = nums.length; // 5

We can leverage .length in loops to populate values:

for(int i = 0; i < nums.length; i++) {
  // Set element at index i
}

Checking .length is also essential to avoid going past the bounds of the array, which will throw an exception:

int[] nums = new int[3]; 

nums[0] = 1; // OK
nums[1] = 2; // OK 

nums[3] = 3; // IndexOutOfBoundsException!

Java does not provide a direct way to resize an array – the length is fixed when it‘s created. We‘ll talk more about how to get around this limitation later.

Multi-Dimensional Arrays

So far we‘ve looked at single-dimensional arrays – rows of elements. But we can also create arrays of arrays, known as multi-dimensional arrays.

The simplest form is a two-dimensional matrix with rows and columns:

2D array diagram

To declare a 2D array in Java:

int[][] matrix; 

The two sets of brackets indicate a 2D structure. We can visualize this as a table.

To actually instantiate the array, we specify both dimensions:

int[][] matrix = new int[3][5]; // 3 rows, 5 columns

Now we can think of matrix as a table with cells that can store integers.

To access elements, we use two indexes – row first, column second:

matrix[1][2] = 5; // Set cell at row 1, col 2 to 5

int val = matrix[2][4]; // Get cell at row 2, col 4

The outer index selects the row, inner index selects column.

Here‘s a complete 2D array example:

int[][] grid = new int[3][3];

// Populate values  
for(int row = 0; row < 3; row++) {
  for(int col = 0; col < 3; col++) {
    grid[row][col] = row + col; 
  }
}

// Print
for(int[] row : grid) {
  for(int n : row) {
    System.out.print(n + " "); 
  }
  System.out.println();  
}

// Prints:
// 0 1 2 
// 1 2 3
// 2 3 4

We can apply the same principles to create 3D and even higher-dimensional arrays!

Arrays vs. ArrayLists

Java arrays have a flaw – their size is fixed at creation. This can lead to issues:

  1. You may allocate more memory than needed upfront
  2. Or you may have to reallocate and copy data if you exceed capacity

For dynamically resizable arrays, Java provides the ArrayList class. Let‘s contrast some key differences:

Array

  • Fixed size
  • Can store primitives directly
  • Faster access by index

ArrayList

  • Resizable, no fixed size
  • Stores object references
  • Slower than arrays

ArrayLists overcome the limitations around size. But arrays are still better optimized for direct access.

So if you need speed and don‘t mind fixed size, use arrays. But if you need dynamism and flexibility, choose ArrayList.

This chart summarizes the growth in ArrayList usage over arrays:

Array vs ArrayList Popularity

As you can see, ArrayList has rapidly dominated arrays in recent years. The flexibility is usually worth the minor performance hit.

Sorting Arrays

A common task is sorting array elements into order.

The Arrays class provides some handy methods so we don‘t have to write the sorting code ourselves.

To sort an array in ascending order:

int[] nums = {5, 1 ,2, 7};
Arrays.sort(nums); // {1, 2, 5, 7}

For primitive types like int, this uses a tuned quicksort internally. For objects, it uses a stable merge sort.

We can also pass a custom Comparator to control the sort order:

// Sort by descending 
Arrays.sort(nums, Comparator.reverseOrder()); 

What about multi-dimensional arrays? For those, we need to convert to a 1D array first:

int[][] matrix = { {5, 2}, {8, 1} };

// Flatten to 1D array  
int[] flat = Arrays.stream(matrix)
  .flatMapToInt(a -> Arrays.stream(a))
  .toArray();

// Now we can sort  
Arrays.sort(flat);

This flattens the 2D structure down to a 1D array that can be sorted.

Searching Arrays

To check if an array contains a particular value, we can leverage Arrays.binarySearch():

String[] months = {"Jan", "Feb", "Mar"}; 

int index = Arrays.binarySearch(months, "Feb"); // 1

This performs a very fast binary search instead of linear scan.

If the value isn‘t found, binarySearch returns a negative number for the insert position:

int index = Arrays.binarySearch(months, "Apr"); // -4

We can also implement search manually with a linear scan:

public static int search(int[] array, int target) {
  for(int i = 0; i < array.length; i++){
    if(array[i] == target) {
      return i; // Found at index i
    }
  }

  return -1; // Not found
}

Linear search runs in O(n) time compared to O(log n) for binary search.

Copying Arrays

There are a few ways we can copy or partially copy arrays in Java:

1. Using arraycopy()

This static method from System can be used to copy data from one array to another:

int[] source = {1, 2, 3};
int[] destination = new int[3]; 

System.arraycopy(source, 0, destination, 0, 3);

It takes the source array, source position, destination, destination position, and length to copy.

2. Using Arrays.copyOf()

This makes a new copy of the array up to the given length:

int[] copy = Arrays.copyOf(source, 3);

We can truncate or expand, as needed.

3. Copying ranges with copyOfRange()

To copy a portion of an array, use copyOfRange():

int[] subset = Arrays.copyOfRange(source, 0, 2); // elements 0, 1 

The range is specified by start (inclusive) and end (exclusive) indexes.

4. Cloning arrays

For object arrays, we can directly call clone() to copy:

Book[] books = getBookArray();
Book[] copiedBooks = books.clone();

This performs a shallow copy, meaning object references are copied but not the internal state.

Common Array Algorithms

The core aspects of arrays – indexed access and linear memory – make them well-suited for all kinds of algorithms. Here are some examples:

Iteration – process each element in sequence:

double[] values = {1.2, 5.3, 7.4};

for(double value : values) {
  System.out.println(value);
} 

Searching – scan linearly for a target value:

boolean contains = false;

for(double value : values) {
  if(value == target) {
    contains = true;
    break;
  }
}

Filtering – store qualifying elements in new array:

double[] positives = new double[values.length];
int index = 0;

for(double value : values) {
  if(value > 0) {
    positives[index++] = value; 
  }
}

Sorting – reorder elements into sequence:

// Bubble sort
for(int i = 0; i < values.length; i++) {
  for(int j = 1; j < values.length - i; j++) {    
    if(values[j] < values[j-1]) {
       // Swap values[j] & values[j-1]
    } 
  }
} 

These are just a taste of the algorithms enabled by arrays! Feel free to get creative.

Associative Arrays with Maps

Unlike some languages, Java doesn‘t have built-in associative arrays – arrays that use names/strings for indexes rather than integers.

But we can implement associative arrays in Java with Map and HashMap:

Map<String, Integer> points = new HashMap<>();

points.put("John", 10);
points.put("Mary",15);

int score = points.get("John"); // 10 

The key difference is that we use objects like String instead of int for the indexes. Under the hood, these get converted to hash codes.

Maps have powerful advantages:

  • Flexible keys – string, objects, etc.
  • Efficient lookup by key
  • Unique keys

Overall, maps provide a great associative array implementation in Java.

Limitations of Arrays

Despite their usefulness, arrays do come with some challenges:

  • Fixed size – cannot resize dynamically
  • Store only one type – lack heterogeneity
  • Slow inserts and deletes – require shifting
  • Not thread-safe – need synchronization
  • Bounded at index 0 – no negative indexes

Collection classes like ArrayList help overcome some limitations. Multithreading requires copies of arrays or thread-safe collections.

So while arrays are indispensable in Java, be aware of where alternatives like List and Set may be more appropriate.

Putting It All Together

Let‘s review the key array concepts we covered:

  • Declaring arrays by type and size
  • Constructing instances with new
  • Accessing elements via index
  • Iterating over arrays with loops
  • Manipulating data with helpers like sort()
  • Multidimensional arrays for matrices
  • Resizable arrays with ArrayList
  • Associative arrays with Map
  • Algorithms like search, filter, map, reduce

Arrays are your "bread and butter" for managing ordered data sets in Java. Start simple, but explore advanced usages like custom collection protocols.

Whether it‘s stocks, genomes, weather data, or game physics, arrays provide the foundation for incredible Java applications. Use them wisely and there‘s no limit to what you can achieve!

I hope this guide gets you off to a strong start using arrays in your Java programs. Feel free to reach out if you have any other array questions!