DEV Community

Cover image for Arrays in Java: A Deep Dive
Marta Kravchuk
Marta Kravchuk

Posted on

Arrays in Java: A Deep Dive

Arrays are a fundamental data structure in Java, used to store collections of elements of the same type. This article provides a comprehensive guide to working with arrays, covering everything from basic declaration and initialization to multidimensional arrays, searching, sorting, comparing and copying. In addition, the material provides essential insights whether you’re preparing for an interview or looking to brush up on your knowledge.


Table of Contents

  1. Array Declarations
  2. Creating an Array
  3. Working with Arrays
  4. Shallow Copy vs. Deep Copy

In Java, an array is a container object that holds a fixed number of elements of the same data type. These elements can be either primitive types (like int, char, boolean, etc.) or objects (like String or custom classes) and arrays implementing interfaces (like CharSequence, etc.). Array functionality is part of the java.util.Arrays package.

Key characteristics of Java arrays include:

  • The length of an array is established upon creation and cannot be changed.
  • All elements in an array must be of the same type.
  • Arrays can store duplicate values.
  • Arrays can be one-dimensional, two-dimensional, or multi-dimensional by representing a matrix of data. Note that "true support" of multi-dimensional arrays is not supported, but Java offers an array of arrays to support a multi-dimensional structure.
  • An array declared with a superclass or interface type can store subclass objects, supporting polymorphism. For example, an array of CharSequence can store references to String and StringBuilder objects.
  • Array elements are accessed using an index, starting at 0. Therefore, the last element in an array is at index length - 1.

1. Array Declarations

Arrays in Java must adhere to specific declaration rules to be properly defined. The following rules outline the correct syntax for declaring arrays:

1️⃣. Data Type: Arrays can store elements of any valid Java data type, including primitive, class, and interface types.
2️⃣. Brackets: The square brackets [] indicate that a variable is an array.

  • One-Dimensional Arrays: One set of brackets is used. The brackets can be placed either after the data type or after the variable name.
int[] numbers;    // brackets next to the type
String fruits[];  // brackets next to the variable name
Enter fullscreen mode Exit fullscreen mode
  • Multidimensional Arrays: Sets of brackets are used on both sides to create a multidimensional array, like a table.
int[][] matrix;               // 2D array
int matrix[][];               // 2D array
int[] matrix[];               // 2D array
int[] matrix [], space [][];  // 2D array and 3D array
Enter fullscreen mode Exit fullscreen mode
  • No Size in Declaration: The size of the array is not specified during declaration. The size is determined later, during array creation.
int[] numbers;           // Declaration
numbers = new int[5];    // Initialization with size
Enter fullscreen mode Exit fullscreen mode

To consolidate these rules, consider the following examples of valid and invalid array declarations.

Valid Array Declarations:

  • Multiple variables of the same type can be defined on one line, including both arrays and non-array variables. Here, a and c are int variables, while b is an array of integers (i.e., int[]):
int a, b[], c;
Enter fullscreen mode Exit fullscreen mode
  • The following declaration implies that d and e are of type int[] whereas, the variable f is a two-dimensional array (i.e., int[][]):
int[] d, e, f[];
Enter fullscreen mode Exit fullscreen mode

Invalid Array Declarations:

  • Size is not part of the declaration:
int[2] intArray;    // compilation error
int intArray[2];    // compilation error
Enter fullscreen mode Exit fullscreen mode
  • You cannot define multiple variables of different types in the same line, separated by commas. Java is strongly typed and this is invalid:
int a, float b[];   // compilation error
Enter fullscreen mode Exit fullscreen mode

2. Creating an Array

Once an array is declared, it must be created. Java provides several ways to create and initialize arrays, each with its own characteristics:

1️⃣. The new Operator and Size: The new operator can be used to define the size of the array within brackets without specifying the initial values:

int[] intArray = new int[10];           // creates an array of int with 10 elements initialised to 0
String[] atringArray = new String[10];  // creates an array of String with 10 elements, all initialised to null

Enter fullscreen mode Exit fullscreen mode

❗ Once an array's size is defined during creation, it cannot be changed. This means an existing array object cannot be resized directly.

int[] numbers = new int[5];
numbers = new int[10];   // not resizing! This creates a new array
Enter fullscreen mode Exit fullscreen mode

In this example, the size of the original array object is not changed. Instead, it creates a new int[] array with a size of 10 and assigns its reference to the numbers variable. The original array (with a size of 5) is now eligible for garbage collection, assuming it is no longer referenced elsewhere. If it had some data, it's lost.

When an array is declared and created, but no values have been assigned, each slot in the array has a default value:

  • Numeric primitives are set to 0.
  • Boolean primitives are set to false.
  • References, including primitive data type wrappers, are set to null.
String names[];
Enter fullscreen mode Exit fullscreen mode

This array points to null. The code never instantiated the array, so it is just a reference variable to null.

String names[] = new String[6];
Enter fullscreen mode Exit fullscreen mode

It is an array because it has brackets. It is an array of type String since that is the type mentioned in the declaration. It has six elements because the length is 6. Each of those six slots currently is null but can potentially point to a String object.

2️⃣. The new Operator and Values: The new operator can be used without specifying the size in brackets, provided the initial values are specified directly within curly braces {}:

int[] numbers = new int[] {42, 55, 99}; // int array of size 3
Enter fullscreen mode Exit fullscreen mode

In this example, an int array of size 3 is created, and its elements are immediately initialized with the given values. The size of the array is implicitly determined by the number of values provided within the curly braces. Again, it is important to emphasize that the initial definition and size of an array cannot be changed once created.

3️⃣. Array Initializer: An array initializer, a shortcut that allows an array to be created and initialized in one statement. The initial values of the array elements are specified directly using curly braces {}. This approach is called an anonymous array:

int[] intArray = {4, 7, 9};
String[] stringArray = {"one", null, "two"};
Enter fullscreen mode Exit fullscreen mode

4️⃣. And finally, let's consider how to create multidimensional arrays. The most important thing to remember about multidimensional arrays in Java is that they are essentially arrays of arrays. This allows you to represent data in a tabular format (rows and columns) or in higher-dimensional structures. While Java doesn't have true built-in multidimensional arrays, it provides the ability to create arrays where each element holds a reference to another array.

  • Declaration and Initialization with Size: The size of a multidimensional array can be specified during declaration and initialization:
String[][] matrix = new String[3][2];    // сreates a 2D array of 3 rows and 2 columns

Enter fullscreen mode Exit fullscreen mode

This creates an array named matrix with three elements. Each of these three elements is a reference to an array of two String objects. It's helpful to think of the addressable range as [0][0] through [2][1]. To visualize this matrix, let's take a look at the code.
For example, suppose one of these values is set as:

matrix[0][1] = "set";
Enter fullscreen mode Exit fullscreen mode

Schematically, this can be depicted as follows:

[0,0] [0,1]  //Here we set a value for matrix[0][1]
[1,0] [1,1]
[2,0] [2,1]
Enter fullscreen mode Exit fullscreen mode
  • Asymmetric Arrays: Multidimensional arrays don't have to be rectangular. Asymmetric arrays can be created where each row has a different number of columns:
int[][] matrix = {{1, 4}, {3}, {9,8,7}};
Enter fullscreen mode Exit fullscreen mode

Schematically, this can be depicted as follows:

[0,0] [0,1]         => {1,4}
[1,0]               => {3}
[2,0] [2,1] [2,2]   => {9,8,7}
Enter fullscreen mode Exit fullscreen mode

Another way to create an asymmetric array is to initialize just an array’s first dimension and define the size of each array component in a separate statement:

int[][] matrix = new int[3][];
matrix[0] = new int[]{4, 9, 7};
matrix[1] = new int[2];
Enter fullscreen mode Exit fullscreen mode

And here an example visual how does work:

[0,0] [0,1] [0,2]     => {4, 9, 7}
[1,0] [1,1]           => {0, 0}
[2,0]                 => null
Enter fullscreen mode Exit fullscreen mode
  • You can easy create matrix in tabular format like example:
int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
Enter fullscreen mode Exit fullscreen mode

To consolidate these rules, consider the following examples of valid and invalid array creation statements.

Valid Array Creation Statements:

  • The new operator is used to allocate memory for the array, specifying its data type and size:
int[] myIntArray = new int[30];        // Brackets associated with the type
short myShortArray[] = new short[20];  // Brackets associated with the variable name

Enter fullscreen mode Exit fullscreen mode
  • An array can be declared first, and then initialized later using the new operator:
String[] myStringArray;
myStringArray = new String[12];
Enter fullscreen mode Exit fullscreen mode
  • The initial values of the array elements are directly specified using {}:
String[] myArray = {"one", "two", null, "three"};    // An array of strings with 4 elements

Enter fullscreen mode Exit fullscreen mode
  • An array with a length of zero can be created:
String[] myArray = {}; // This array has a length of 0
Enter fullscreen mode Exit fullscreen mode
  • You can declare and initialize variables like array and non array, but you need to know that both can have same type, it's also you can chain create variables like that as:
int[] mySecondIntArray, myIntArray = mySecondIntArray = new int[50]; // Initializes both arrays

Enter fullscreen mode Exit fullscreen mode

But if you want to create two array, and you give to only one parameter this as will work as:

int[] myIntArray, mySecondIntArray = new int[50]; // The second array is initialized to size 50, but the "first" array is not initialized.

Enter fullscreen mode Exit fullscreen mode
  • A reference-type array can be explicitly set to null:
int[] mySecondIntArray = null;
Enter fullscreen mode Exit fullscreen mode
  • As arrays are ultimately objects in Java, they can be assigned to variables of type Object:
Object o = new int[5];     // Valid initialization and assignment to an Object

Enter fullscreen mode Exit fullscreen mode
  • The new operator can be combined with the array initializer, but the size cannot be specified in the brackets:
int a[] = new int[]{1, 2, 3, 4, 5};    // Size is determined by the initializer

Enter fullscreen mode Exit fullscreen mode
  • This creates a two-dimensional array:
String[] stringArray[] = {{"one", "two"}, {"three", "four"}};
Enter fullscreen mode Exit fullscreen mode
  • This is a valid two-dimensianal array declaration and initialization. Only the first dimension is required to have a size, because you can have an array of arrays, and the arrays referenced by the element indices can be of different sizes:
int[][] matrix = new int[2][]; 
Enter fullscreen mode Exit fullscreen mode

Invalid Array Creation Statements:

  • The parentheses in this statement generate a compile error and size needs to be on the right side of the equation:
String[] stringArray = new String(){3};   // compilation error
Enter fullscreen mode Exit fullscreen mode
  • Size needs to be on the right side of the equation:
String[3] stringArray = new String[];     // compilation error
Enter fullscreen mode Exit fullscreen mode
  • An array initializer doesn't require a restatement of the array type:
int a[] = int[]{1, 2, 3, 4, 5};           // compilation error
Enter fullscreen mode Exit fullscreen mode
  • This is invalid because you are stating the size, but the array initilizer already defines the size:
int b[] = new int[5] {1, 2, 3, 4, 5};     // compilation error
Enter fullscreen mode Exit fullscreen mode
  • You cannot use the array initializer on a separate statement line from the declaration of the array:
String[] myArray; 
myArray = {"one", "two", null, "three};  // compilation error
Enter fullscreen mode Exit fullscreen mode
  • This statement is invalid, unlike using the new operator on the right hand side of this equation. You cannot use array initializer in a compound statement:
short[] mySecondShortArray, myShortArray = mySecondShortArray = {1, 2, 3, 4, 5};   // compilation error

Enter fullscreen mode Exit fullscreen mode
  • The type of second argument is incorrect. Only the first dimension in two-dimensional array is required to have a size. And this initialization does not give it a size, so it is incorrect:
int[][] matrix = new int[][2];            // compilation error
Enter fullscreen mode Exit fullscreen mode
  • You cannot initialize a two-dimensional array and assign it to a one-dimensional array:
int c[] = new int[5][3];                  // compilation error
Enter fullscreen mode Exit fullscreen mode

3. Working with Arrays

Once you've declared and created an array, it's time to use it. This section covers common operations and functionalities associated with working with arrays in Java.

  • Arrays in Java are objects, which means they inherit certain characteristics from the Object class. So, arrays can call equals() method. Remember, this would work even on an int[], too. Since int is a primitive, therefore int[] is an object.
String[] bugs = {"cricket", "beetle", "ladybug};
String[] alias = bugs;
bugs.equals(alias);                          // true
Enter fullscreen mode Exit fullscreen mode
  • Java has provided the Arrays.toString() method that prints an array:
System.out.println(bugs.toString());         // Output: [Ljava…
System.out.println(Arrays.toString(bugs));   // Output: [cricket, beetle, ladybug]

Enter fullscreen mode Exit fullscreen mode
  • The length property gives the number of slots have been allocated. This property is a field, not a method, it should not contain parentheses:
String[] birds = new String[6];
System.out.println(birds.length);        // Output: 6
Enter fullscreen mode Exit fullscreen mode

Sorting Arrays

Java provides a convenient Arrays.sort() method for sorting arrays in ascending order. When sorting arrays containing different data types (numbers, strings, etc.), it's important to be aware of the rules Java uses to compare these types, as this will determine the final order of the elements.

Syntax:

void Arrays.sort(T[] array)
Enter fullscreen mode Exit fullscreen mode

The easy way with numbers:

int[] numbers = { 6, 9, 1 };
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));    // Output: [1, 6, 9]
Enter fullscreen mode Exit fullscreen mode

Try this again with String types:

String[] strings = {"10", "9", "a", "100", "M", "0};
Arrays.sort(strings);
System.out.println(Arrays.toString(strings)); // Output: [0, 10, 100, 9, M, a]
Enter fullscreen mode Exit fullscreen mode

String sorts in alphabetic order, and 1 sorts before 9. The numbers are sorted before letters, and uppercase sorts before lowercase.

Searching Arrays

Java provides a convenient method, Arrays.binarySearch(), to efficiently search for a specific element within an array. This method requires that the array already be sorted for the results to be accurate.

Syntax:

int Arrays.binarySearch(int[] array, int key)
int Arrays.binarySearch(Object[] array, Object key)
Enter fullscreen mode Exit fullscreen mode

The rules for searching:

  • If the target element is found in the sorted array, the method returns the index of the matching element (a positive integer).
  • If the target element is not found in the sorted array, the method returns a negative value. The returned value can be used to determine the insertion point for the element to maintain the sorted order. The formula is [-n] - 1, where n is an imaginary index to insert to keep the sorted order.
int[] numbers = {2, 4, 6, 8};
Arrays.binarySearch(numbers, 2);   // 0: match
Arrays.binarySearch(numbers, 4);   // 1: match
Arrays.binarySearch(numbers, 1);   // -1: insertion point would be index 0 => [-0] - 1 = -1
Arrays.binarySearch(numbers, 3);   // -2: [-1] - 1 = -2
Arrays.binarySearch(numbers, 9);   // -5: [-4] - 1 = -5

Enter fullscreen mode Exit fullscreen mode

It is critical to understand that Arrays.binarySearch() only works correctly on sorted arrays. If you use it on an unsorted array, the results are unpredictable and likely incorrect. The only way can make sort (if it's necessary):

int[] numbers = new int[]{3, 2, 1};
Arrays.binarySearch(numbers, 2);        // 1: match
Arrays.binarySearch(numbers, 3);        // output not predictable: -4

String[] strings = new String[]{"9", "100", "10"};
Arrays.sort(strings);                         // Now: [10, 100, 9]
Arrays.binarySearch(strings, "10");           // 0: match
Arrays.binarySearch(strings, "8");            // -3: [-2] - 1 = -3
Enter fullscreen mode Exit fullscreen mode

Comparing Arrays

Java provides two methods, Arrays.compare() and Arrays.mismatch(), to compare two arrays to determine which is smaller.

Syntax: Arrays.compare()

<T> int Arrays.compare(T[] array1, T[] array2)
Enter fullscreen mode Exit fullscreen mode

The method returns:

  • A negative integer is if the first array is less than the second array.
  • Zero if the arrays are equal.
  • A positive integer is if the first array is greater than the second array.

The rules that define a smaller value:

  • null is smaller than any other value.
  • For numbers, normal numeric order applies.
  • For strings, one is smaller if it is a prefix of another.
  • For strings/characters, numbers are smaller than letters.
  • For strings/characters, uppercase is smaller than lowercase.
Arrays.compare(
    new int[] {1, 2}, new int[] {1});           // 1
Enter fullscreen mode Exit fullscreen mode

Result: Positive number
Reason: The first element is the same, but the first array is longer.

Arrays.compare(
    new int[] {1, 2}, new int[] {1, 2});        // 0
Enter fullscreen mode Exit fullscreen mode

Result: Zero
Reason: Exact match

Arrays.compare(
    new String[] {"a"}, new String[] {"aa"});   // -1
Enter fullscreen mode Exit fullscreen mode

Result: Negative number
Reason: The first element is a substring of the second.

Arrays.compare(
    new String[] {"a"}, new String[] {"A"});    // 32
Enter fullscreen mode Exit fullscreen mode

Result: Positive number
Reason: Uppercase is smaller than lowercase.

Arrays.compare(
    new String[] {"a"}, new String[] {null});   // 1
Enter fullscreen mode Exit fullscreen mode

Result: Positive number
Reason: null is smaller than a letter.

Arrays.compare(
   new String[] {"1"}, new String[] {"a"});     // -48
Enter fullscreen mode Exit fullscreen mode

Result: Negative number
Reason: Numbers are smaller than letters

Finally, this code does not compile because the types are different. When comparing two arrays, they must be the same array type.

Arrays.compare(
   new int[] {1}, new String[] {"a"});          // DOES NOT COMPILE
Enter fullscreen mode Exit fullscreen mode

Syntax: Arrays.mismatch()

<T> int mismatch(T[] array1, T[] array2)
Enter fullscreen mode Exit fullscreen mode

If the arrays are equal, mismatch() returns -1. Otherwise, it returns the first index where they differ.

The method returns:

  • If the arrays are equal, it returns -1
  • If one array is null, it returns 0
  • If the arrays are not equal, it returns the first index where they differ
Arrays.mismatch(
   new int[] {1}, new int[] {1});                  // -1

Arrays.mismatch(
   new String[] {"a"}, new String[] {"A"});        // 0

Arrays.mismatch(
   new String[] {"a"}, new String[] {null});       // 0

Arrays.mismatch(
   new int[] {1, 2}, new int[] {1});               // 1
Enter fullscreen mode Exit fullscreen mode

4. Shallow Copy vs. Deep Copy

Each type of array implements the Cloneable and Serializable interfaces. Therefore, it's logical to discuss the clone() method, which is related to two important concepts: shallow and deep copy.

Shallow Copy

A shallow copy creates a new object (or array) but does not clone the objects contained within. Instead, it copies the references to the same objects in memory. As a result, changes made to the objects through one reference will be reflected in the other reference.

@Setter
@Getter
public class Friend implements Cloneable {

    private String name;
    private int age;

    public Friend(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public Friend clone() {
        return new Friend(this.name, this.age);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Shallow Copy of Primitive Array:
private static void shallowCopyOfPrimitive() {
    int[] a = {1, 2, 3};
    int[] copy = a.clone();

    copy[1] = 4;
    System.out.println("Origin: " + Arrays.toString(a));
    System.out.println("Copy: " + Arrays.toString(copy)); 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Origin: [1, 2, 3]
Copy: [1, 4, 3]
Enter fullscreen mode Exit fullscreen mode

As primitives are copied directly, the original array remains unchanged, illustrating that primitive types are independent when cloned.

  • Shallow Copy of Object Array:
private static void shallowCopyOfObject() {
    Friend[] a = {new Friend("Monica", 28), new Friend("Ross", 26)};
    Friend[] copy = a.clone();

    copy[0].setName("Chandler");
    System.out.println("Origin: " + Arrays.toString(a)); 
    System.out.println("Copy: " + Arrays.toString(copy)); 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Origin: [Name: Chandler, Age: 28, Name: Ross, Age: 26]
Copy: [Name: Chandler, Age: 28, Name: Ross, Age: 26]
Enter fullscreen mode Exit fullscreen mode

This shows the behavior of shallow copying, where both arrays point to the same underlying Friend objects. Changes in one affect the other because they share the same references.

Deep Copy

A deep copy creates a new object (or array) and also clones the objects contained within. This means that two independent objects are created in memory, so changes made to one do not affect the other.

private static void deepCopy() {
    Friend[] a = {new Friend("Monica", 28), new Friend("Ross", 26)};
    Friend[] copy = new Friend[a.length];

    for (int i = 0; i < a.length; i++) {
        copy[i] = a[i].clone();     // Calling clone method to create independent copies
    }

    copy[0].setName("Chandler");
    System.out.println("Origin: " + Arrays.toString(a)); 
    System.out.println("Copy: " + Arrays.toString(copy)); 
}

Enter fullscreen mode Exit fullscreen mode

Output:

Origin: [Name: Monica, Age: 28, Name: Ross, Age: 26]
Copy: [Name: Chandler, Age: 28, Name: Ross, Age: 26]
Enter fullscreen mode Exit fullscreen mode

This illustrates deep copying, where modifications in one array do not impact the other because they hold separate object instances in memory.


Thanks for reading! Hope you found some useful info in this article. Got any questions or ideas? Drop them in the comments below. Stay tuned here for a new Java series every week! And feel free to connect with me on LinkedIn 😊

Top comments (0)