Java

Working with Arrays in Java

An array in Java is a special data type that combines several values into one unit, like a display case in which the compartments are numbered consecutively.

 

The elements are addressed via an integer index. Each compartment (for Smurfs, for example) always takes values of the same type (only Smurfs, no Pokemon). Normally, the compartments of an array (its elements) are located one after the other in memory, but this implementation detail on the virtual machine (VM) is not visible to programmers.

 

Each array contains values of only one specific data type or basic type, namely, the following:

  • Elementary data types (like int, byte, long, etc.)
  • Reference types
  • Reference types of other arrays to form multidimensional arrays

Basic Components

Working with arrays requires you to learn three new things:

  1. Declaring array variables
  2. Initializing array variables as well as allocating space
  3. Accessing arrays, with both read and write access

Here’s an example:

  1. Declare a variable named prices that references an array:

double[] prices; 

  1. Initialize the variable with an array object of size 10:

prices = new double[ 10 ]; 

  1. Assign a random number to the first element and assign twice the value of the first element to the second element:

    prices[ 0 ] = Math.random();
    prices[ 1 ] = prices[ 0 ] * 2;

Note how square brackets are used in several places: once to declare the type, then to build the array, then to write to arrays and to read from arrays. We’ll now describe these three elements in more detail.

 

Terms of an Array

 

Declaring Array Variables

Declaring an array variable is similar to an ordinary declaration except that the characters [ and ] are placed after the data type. Let’s use the prices variable again, which is intended to store prices, as defined in the following way:

 

double[] prices;

 

The declaration has two types of information at its core: that it “is an array” and that it will “store elements of type int.”

 

The square brackets are tokens, so whitespace isn’t mandatory for separation. Another valid example is the following:

 

double[]prices;

 

Placing the square brackets before the variable is also syntactically valid but not at all recommended:

 

double []prices; // Violation of the usual Java style guide

 

Note that the square brackets can also be placed after the identifier name when declaring an array variable, but the declaration is slightly different in this case. This difference becomes apparent when more than one variable is declared, as in the following example:

 

double []prices,

matrix[], threeDimMatrix[][];

 

This code corresponds to the following declaration:

 

double prices[], matrix[][], threeDimMatrix[][][];

 

The following example is more neatly written:

 

double[] prices;

double[][] matrix;

double[][][] threeDimMatrix;

 

To avoid errors of this kind, each line should contain only one declaration of a type. In any case, according to pure Java doctrine, the brackets should be placed after the type identifier, as Java creator James Gosling intended.

 

For arrays with non-primitive elements, the data type of the array elements doesn’t have to be a primitive data type. An array of object references can also be declared. This array then only stores references to actual objects. The size of the array in memory is therefore calculated from the length of the array multiplied by the memory requirement of a reference variable. Only the array object itself is created, not the objects the array will store simply because the compiler wouldn’t even know which constructor to call.

 

For example, the following example declares two array variables:

 

String[] names;

Point[] locations;

 

Creating Array Objects with New

Creating the array reference variable alone doesn’t create an array of a specific length. In Java, the creation of the array is as dynamic as the nature of object creation. An array must be created using the new keyword because arrays are also objects.2 The length of the array is specified in square brackets, which can be any integer value or even a variable. Even 0 is possible. Later, the size of the array cannot be changed.

 

For example, the following example creates an array of ten elements:

 

double[] prices;

prices = new double[ 10 ];

 

The array declaration can also occur together with the initialization, as in the following example:

 

double[] prices = new double[ 10 ];

 

The Java virtual machine (JVM) initializes the arrays by default, for instance, with 0, 0.0 or false for primitive values and with null for references.

 

Arrays are pretty normal objects. Several indications prove that arrays are objects, namely, the following:

  • A special type of the new notation creates a copy of the array class; new always reminds us that an object is being built at runtime.
  • An array object has an object variable named length, and methods are declared on the array object, such as clone() and everything that long.Object has.
  • The == and != operators follow their meaning as it relates to objects: These operators only compare whether two variables reference the same array object, but in no case do these operators evaluate the contents of the arrays (although equals(...) can).

Access to array elements via the square brackets [] can be understood as a hidden call via secret methods like array.get(index). The [] operator isn’t provided for other objects.

 

Arrays with { Contents }

The previous declarations of array variables don’t create an array object that can store the individual array elements. However, if the entries are to be assigned values directly, a shortcut in Java can automatically create an array object and assign values to it at the same time.

 

For example, consider the following example of a value assignment of an array during its initialization:

 

double[] prices = { 2.99, 3.10, 4.40 + 0.90 };

String[] names = {

   "Caramellos," "Gummi Fish."

   "Starbursts".toUpperCase(), // STARBURSTS

new StringBuilder( "M" ).append( '&' ).append( 'M' ).toString() // M&M

};

 

In this case, an array of suitable size is created, and the elements named in the enumeration are copied into the array. Dynamic calculations at runtime are possible because the values don’t need to be fixed.

 

Note that empty arrays without contents are also allowed. The arrays are initialized but have no elements, and their length is 0, as in the following examples:

 

String[] names = {};

or

double[] prices = new int[0]

 

You can place a comma before the closing curly bracket, so that double[] prices = { 2, 3, }; is valid. This syntax simplifies adding elements but doesn’t produce any empty element. Even the following is possible in Java: double[] prices = { , };.

 

The declaration of an array variable with initialization doesn’t work with var, as in the following example:

 

var prices = { 2, 3 }; // Array initializer is not allowed here

 

Reading the Length of an Array via the Object Variable Length

The number of elements an array can store is called its size or length and is stored for each array object in the freely accessible object variable length, which is a public-finalint variable whose value is either positive or null. The size can’t be changed subsequently.

 

In this example, the following code creates an array and then outputs its length:

 

int[] prices = { 2, 3, 5, 7, 7 + 4 };

System.out.println( prices.length ); // 5

 

Array Lengths Are Final. The object variable attribute length of an array isn’t only public and of type int, but of course also final. Write access isn’t allowed because a dynamic enlargement of an array isn’t possible; write access leads to a translation error.

 

Other containers also have a length, which is usually requested via a method. Beginners often are confused when, for example, the length of a string is queried via a length() method, the number of elements in an array via the length attribute, and in the ArrayList data structure via size().

 

Accessing the Elements Via the Index

The elements of an array are accessed using the square brackets [] placed after the reference to the array object. In Java, an array starts at index 0 (and not at a freely selectable lower limit as in Pascal). Since the elements of an array are numbered starting at 0, the last valid index is 1 less than the length of the array. Thus, for an array a of length n, the valid range is a [0] to a[n - 1].

 

Since the variables are accessed via an index, these variables are also called indexed variables.

4

The following example accesses the first and last characters from the array:

 

char[] name = { 'C', 'h', 'r', 'i', 's' };

char first = name[ 0 ]; // C

char last = name[ name.length - 1 ]; // s

 

The next example runs the entire array with prices and outputs the positions starting at 1:

 

double[] prices = { 2, 3, 5, 7, 11 };

for ( int i = 0; i < prices.length; i++ ) // Index: 0 <= i < 5 =

// prices.length

System.out.printf( "%d %s%n", i + 1, prices[ i ] );

 

Instead of just running an array and outputting the values, our next program will calculate and output the arithmetic mean of prices.

 

public class PrintTheAverage {

 

   public static void main( String[] args ) {

      double[] numbers = { 1.9, 7.8, 2.4, 9.3 };

 

      double sum = 0;

 

      for ( int i = 0; i < numbers.length; i++ )

          sum += numbers[ i ];

 

      double avg = sum / numbers.length;

      System.out.println( avg ); // 5.35

   }

}

 

The array must have at least one element; otherwise, an exception will occur when dividing by 0.

On the Type of the Index*

Inside the square brackets is a positive integer expression of type int, which must be computable at runtime. long values, boolean, floats, or references aren’t possible; however, int enables the use of more than two billion elements. With regard to floats, the question of the access technique would remain. In this case, we would have to reduce the value to an interval.

 

Note that the index of an array must be of type int, and this also includes type conversions from byte, short, and char. An index of type char is favorable, for example, as a run variable, when arrays of characters are generated, as in the following example:

 

char[] alphabet = new char[ 'z' - 'a' + 1 ]; // 'a' equals 97 and 'z' 122

for ( char c = 'a'; c <= 'z'; c++ )

   alphabet[ c - 'a' ] = c; // alphabet[0]='a', alphabet[1]='b', etc.

 

Strictly speaking, in this case, we’re also dealing with index values of the type int because the char values are still calculated beforehand.

Strings Are Not Arrays*

An array of char characters has a completely different type than a String object. While square brackets are allowed for arrays, the String class doesn’t provide access to characters via []. However, the String class provides a constructor so that a String object can be created from an array of characters. All characters of the array are copied, and then afterwards, the array and string have no more connection. Thus, if the array later changes, the string won’t automatically change with it. Nor can it since strings are immutable.

 

Typical Array Errors

Errors may occur when accessing an element of an array. First, the array object may be missing, which means the referencing will fail.

 

In this example, the compiler doesn’t notice the following error, and the penalty is a NullPointerException at runtime.3 Consider the following example:

 

String[] names = null;

names[ 0 ] = 1; // NullPointerException

4

Other errors may be caused by the index. If the index is negative or larger than the size of the array, then an ArrayIndexOutOfBoundsException will occur. Each access to the array is tested at runtime, although the compiler may well find some errors.

 

With the following access attempts, the compiler could theoretically sound an alarm, which the standard compiler wouldn’t do because accessing the elements is syntactically fine even with an invalid index. Consider the following examples:

 

int[] array = new int[ 100 ];

array[ -10 ] = 1; // error at runtime, not at compile time

array[ 100 ] = 1; // error at runtime, not at compile time

 

If the IndexOutOfBoundsException isn’t caught, the runtime system aborts the program with an error message. The fact that the array boundaries are checked is part of Java’s security concept and can’t be turned off. However, this check is no longer a major performance issue today since the runtime environment doesn’t need to check every index to ensure that a block with array access is correct.

Gimmick: The Index and the Increment*

You can use i = i++ as an increment. The statement is treated the same way for an array access, as in the following example:

 

array[ i ] = i++;

 

At position array[i], the value of i is saved, and then the assignment is made. When you construct a loop around an array, you can extend it during initialization, as in the following example:

 

int[] array = new int[ 4 ];

int i = 0;

while ( i < array.length )

   array[ i ] = i++;

 

The initialization results in 0, 1, 2, and 3. This use isn’t recommended due to a lack of clarity.

 

Passing Arrays to Methods

References to arrays can be passed to methods in the same way as references to normal objects. Earlier, we determined the mean value of a number series. The logic for this calculation can be perfectly swapped out to a method, as in the following example:

 

public class Avg1 {

 

   static double avg( double[] numbers ) {

       double sum = 0;

      

       for ( int i = 0; i < numbers.length; i++ )

           sum += numbers[ i ];

 

       return sum / numbers.length;

   }

 

   public static void main( String[] args ) {

      double[] prices = { 2, 3, 4 };

       System.out.println( avg( prices ) ); // 3.0

   }

}

Checking Null References

References always entail the problem that they can be null. A call of avg(null) is syntactically valid. Therefore, an implementation should check for null and report a false argument, as in the following example:

 

if ( numbers == null || numbers.length == 0 )

   throw new IllegalArgumentException( "Array null or empty" );

 

Multiple Return Values*

When we write methods in Java, they can’t have more than one return value when return is used. But if you need to return more than one value, you’ll need another solution. Two possible options can be useful in these cases:

  • Containers, such as arrays or other collections, can combine values and return them as return values.
  • Special containers are passed in which the method places return values; a return statement is no longer necessary.

Let’s consider a static method that returns the sum and the product as an array for two numbers next.

 

static int[] productAndSum( int a, int b ) {

   return new int[]{ a * b, a + b };

}

 

public static void main( String[] args ) {

   System.out.println( productAndSum(9, 3)[ 1 ] );

}

 

Preinitialized Arrays

If we want to create an array object in Java and initialize it with values right away, you can write something like the following code:

 

double[] prices = { 2, 3, 5 };

 

Java doesn’t allow you to initialize array contents after the variable declaration or to use the array even without a variable as argument, as in the following examples:

 

prices = { 2.2, 3.9 }; // compiler error

avg( { 1.23, 4.94 } ); // compiler error

 

An attempt like this example will fail and trigger the compiler message “Array initializer is not allowed here.”

 

Two possible approaches can solve this problem. The first approach includes the introduction of a new variable, in the following example, tmpprices:

 

double[] prices;

double[] tmpprices = { 2 };

prices = tmpprices;

 

The second approach represents a variant of the new notation, which is extended by a pair of square brackets. The initial values of the array then follow in curly brackets. The size of the array corresponds exactly to the number of values. For the earlier examples, the notation would be the following:

 

double[] prices;

prices = new double[]{ 2, 5, 7, 11, 13 };

 

The following notation is also quite handy for method calls when arrays are passed:

 

avg( new double[]{ 1.23, 4.94, 9.33, 3.91, 6.34 } );

 

Since in this case an initialized array with values is passed to the method right away and no additional variable is used, this type of array is referred to as an anonymous array. Actually, other kinds of anonymous arrays also exist, as shown by new int[2000].length, but in this case, the array is not initialized with its own values.

The Truth about Array Initialization*

As nice as the compact initialization of array elements can be, this approach is also runtime and memory intensive. Since Java is a dynamic language, the concept of array initialization doesn’t quite fit into the picture. For this reason, the initialization is performed only at runtime.

 

An array like

 

int[] weights = { 2, 3, 5, 7 };

 

is transformed by the Java compiler and treated in a similar way to the following:

 

int[] weights = new int[ 4 ];

weights[ 0 ] = 2;

weights[ 1 ] = 3;

weights[ 2 ] = 5;

weights[ 3 ] = 7;

 

Only after a moment’s reflection does the extent of the implementation become apparent: First, the methods will have memory requirements. If the weights array is declared in a method and initialized with values, the assignment costs runtime because we have many access attempts, which are also all nicely secured by the index check. In addition, since the bytecode for a single method may only be of limited length due to various restrictions in the JVM, this space can quickly be exhausted for really large arrays. For this reason, we don’t advise storing images or large tables in the program code, for example. In C, a popular approach was to use a program that turned a file into a sequence of array declarations. If doing so is really necessary in Java, consider the following points:

  • You can use a static array (a class variable) so that the array only needs to be initialized once during the program run.
  • If the values are in the byte range, you can convert these values to a string and then later convert the string into an array. This option is a clever way to easily accommodate binary data.

Editor’s note: This post has been adapted from a section of the book Java: The Comprehensive Guide by Christian Ullenboom.

Recommendation

Java: The Comprehensive Guide
Java: The Comprehensive Guide

This is the up-to-date, practical guide to Java you’ve been looking for! Whether you’re a beginner, you’re switching to Java from another language, or you’re just looking to brush up on your Java skills, this is the only book you need. You’ll get a thorough grounding in the basics of the Java language, including classes, objects, arrays, strings, and exceptions. You'll also learn about more advanced topics: threads, algorithms, XML, JUnit testing, and much more. This book belongs on every Java programmer's shelf!

Learn More
Rheinwerk Computing
by Rheinwerk Computing

Rheinwerk Computing is an imprint of Rheinwerk Publishing and publishes books by leading experts in the fields of programming, administration, security, analytics, and more.

Comments