In the previous articles of this series, primitive data types were used in different Java (and C#) program examples to manage data stored inside the computer memory (RAM).
This was done by declaring variables—specific memory areas whose size in bits depends on the associated type—that are available and can be accessed during the program's execution through the corresponding variable identifier. Each variable can store a single value of the associated type.
An array is a data structure that allows the allocation of a sequence of elements of the same data type. When an array is declared, a set of contiguous memory locations is allocated inside the computer's memory. Each location stores an array element that can be accessed via a corresponding integer index. Arrays in Java can be declared using the syntax described below.
public static void main(String[] args)
{
int n = // specifies the number of elements of the array
double[] values = new double[n];
}
The above instructions allocate an array of n
elements of type double
, named values
, in the computer's memory. When a new array is created, its elements are initialised to the default value for their type, such as 0.0d
for double
. A possible graphical representation of the values
array with n
double
elements is shown below. The figure displays the array right after its creation, with no values assigned to its elements, containing the respective default value. Note that 0.0
is used as the default value of the elements (instead of 0.0d
, as it should be for a double
type) to simplify the figure.
As mentioned, It should be noted that all the array elements are allocated in a contiguous area of the memory. The array size is fixed and cannot be modified during a program execution. Moreover, for an array of n
elements, the first is associated with index 0 and the last with index (n-1).
Array Initialisation
Like variables of primitive data types, arrays can also be initialised when declared inside a program, as shown in the code snippet below.
public static void main(String[] args)
{
double[] values = {0.1, 3.76, -1.23, 0.45};
}
The values assigned to each array element are specified between curly braces, separated by a comma. The values will be assigned to the array elements according to the specified order. When the above approach is used to declare and initialise an array, it is not required to define the size of the array explicitly. This will be determined when the program is executed, according to the number of elements provided in between the curly braces.
Note that once an array has been declared inside a program, the above notation cannot be used to assign new values to all the array's elements in a single instruction. However, as described in the next section, values can be assigned to individual array elements.
Accessing the Elements of an Array
In Java (C# and other programming languages), given an array of n elements, the index ranges from 0 to n-1. The array identifier and the index enclosed in []
are used to define assignment instructions where specific array elements can be the lvalue, the rvalue, or both. The code snippet below shows how array elements are used as the lvalue of different assignment instructions, i.e., they are positioned at the left of the assignment operator =
.
public static void main(String[] args)
{
// declare an array of 5 elements: first index 0, last index 4
double[] values = new double[5];
values[2] = 3.4; // value stored in position 3
values[0] = -1.1; // value stored in position 0 (first)
values[4] = -8.7; // value stored in position 4 (last)
}
The next code snippet shows additional examples of how array elements can also be the rvalue of an assignment as they are used at the right of the =
operator.
public static void main(String[] args)
{
int[] values = {1, 7, 3, 5, 4};
int x = values[1] + 3; // {1, 7, 3, 18, 4}, x = 10
values[2] = values[1] + x; // {1, 7, 17, 18, 4}
values[3] = 18; // {1, 7, 3, 18, 4}
}
Arrays and Loops
Often, it is required to perform operations on different elements of an array. Whilst the above instructions provide access to specific elements, loops are the most widely used approach to iterate over a whole array.
It should be noted that in Java, once an array is created, it provides a property—length
—that contains the associated number of elements. This is accessible using the dot notation shown in the following code snippet. A similar property is available in C# as Length
.
public static void main(String[] args)
{
int[] values = new int[12];
int size = values.length; // size contains 12
int[] values2 = { 1, 2, 3, 4, 5 };
int size2 = values2.length; // size2 contains 5
}
The following code snippet shows how a for loop can be used to print the array's content on the screen by accessing each of its elements via the associated index.
public static void main(String[] args)
{
int[] values = {3, 2, 10, 5, 1};
for (int i = 0; i < values.length; i++)
{
System.out.println(values[i]);
}
}
The i
counter is used inside the loop as the index of the values
array. It is initialised to start from 0, i.e., the index of the first array element. i
is then incremented by 1 at each iteration to refer to and access the remaining elements of the array. The code shows that a relational expression using the array property values.length
controls the loop's termination. When i
contains the value 4, the last element of the array is accessed. The subsequent increment of i
from 4 to 5 will make the loop condition false
and cause its termination.
When access to all the elements of an array is anticipated, a for-each loop can be used to iterate over them. Unlike the traditional for loops, for-each loops usually do not maintain an explicit counter. They essentially say do this to everything in this array, rather than do this x times. The code snippet below shows an example of for-each loop in Java.
public static void main(String[] args)
{
int[] values = {3, 2, 10, 5, 1};
for (int v : values)
{
System.out.println(v);
}
}
It can be noted that there is not a specific foreach
keyword in Java. A for-each loop is implemented via the for
keyword. Then, a variable is declared in parenthesis followed by :
and an array's (or, in general, a Collection) identifier on which the loop will iterate. In the above example, because the elements of the array values
are of type int
, the variable v
declared and used inside the for-each is also an int
.
At each iteration, v
will contain one of the elements of the array values
, starting from the first one, i.e., the value 3
stored in position with index 0. The loop terminates when the last element, i.e., the value 1
, stored in position with index 4, is assigned to v
. A programmer does not have to deal with indexes as the Java compiler abstracts and manages the underlying access to the array's elements.
Unlike Java, C# provides a specific foreach
keyword and a slightly different syntax where :
is replaced by the in
keyword, as shown in the code snippet below.
static void Main(string[] args)
{
int[] values = {3, 2, 10, 5, 1};
foreach (int v in values)
{
Console.WriteLine(v);
}
}
Array Size and Potential Error Conditions
When accessing the elements of an array of size n, it is essential to specify an index within the allocated range, i.e., from 0 to n-1. This prevents error conditions referred to out-of-bounds array access from occurring when the associated program is executed.
The following example highlights what happens if an additional instruction attempting to access the array element with index 5 is added to one of the previous code snippets.
public static void main(String[] args)
{
// declare an array of 5 elements: first index 0, last index 4
double[] values = new double[5];
values[2] = 3.4; // value stored in position 3
values[0] = -1.1; // value stored in position 0 (first)
values[4] = -8.7; // value stored in position 4 (last)
values[5] = 2.3; // this array element does not exist!
}
Even though the code could be compiled without errors, its execution would generate a runtime error condition called an exception. Exception handling is a technique in which a programming construct is used to consistently trap, intercept and handle an error that occurred during application execution. The Java runtime is designed to use exception handling based on exception objects and protected code blocks. This is one of the features offered by application virtual machines that run bytecode.
In a C program compiled into machine code, when an out-of-bounds array access occurs, it can lead to undefined behaviour, causing the program to continue running with unpredictable outcomes, crash unexpectedly, produce incorrect results without immediate detection, or reveal issues at some undetermined point in the future.
However, the error management differs in a Java (or C#) program running within a managed environment. Here, the runtime performs checks to prevent out-of-bounds array access, and any attempt to access an element beyond the array's bounds triggers a runtime exception known as ArrayIndexOutOfBoundsException
. The advantage of this approach lies in a programmer being able to catch and handle these exceptions within the code, preventing program crashes and enabling graceful error recovery.
It should be noted that in Java (and C#), the number of elements forming an array can also be specified at runtime, as demonstrated by the following code.
public static void main(String[] args)
{
Scanner s = new Scanner(System.in);
int size = s.nextInt();
// content of size variable is used instead of a constant value
double [] values = new double[size];
}
In the above example, the compiler would not know the content of size
. Hence, as discussed above, in Java (and C#), any checks on the size of arrays and what indexes are used are left to the runtime environment.