"

22 Javanotes 9.0, Section 7.1 — Array Details

Section 7.1

Array Details



Array basics have been discussed
in previous chapters,
but there are still details of Java syntax to be filled in, and there is a lot more to say
about using arrays. This section looks at some of the syntactic details,
with more information about array processing to come in the rest of the
chapter.

To briefly review some of the basics…. An array is a numbered sequence
of elements, and each element acts like a separate variable. All of the
elements are of the same type, which is called the base type of the array.
The array as a whole also has a type. If the base type is btype,
then the array is of type btype[]. Each element in the array
has an index, which is just its numerical position in the sequence of elements
with numbering starting from zero.
If the array is A, then element number i of the array is A[i].
The number of elements in an array is called its length. The length of
an array A is A.length. The length of an array can’t
be changed after the array is created. The elements of the array A
are A[0], A[1], …, A[A.length-1].
An attempt to refer to an array element with an index outside the range
from zero to A.length-1 causes an ArrayIndexOutOfBoundsException.

Arrays in Java are objects, so an array variable can only refer to an array;
it does not contain the array. The value of an array variable can also be null.
In that case, it does not refer to any array, and an attempt to refer to an
array element such as A[i] will cause a NullPointerException.
Arrays are created using a special form of the new operator. For
example,

int[] A = new int[10];

creates a new array with base type int and length 10, and it sets
the variable A to refer to the newly created array.


7.1.1  For-each Loops

Arrays are often processed using for loops. A for
loop makes it easy to process each element in an array from beginning to end. For
example, if namelist is an array of Strings,
then all the values in the list can be printed using

for (int i = 0; i < namelist.length; i++) {
    System.out.println( namelist[i] );
}

This type of processing is so common that there is an alternative form of
the for loop that makes it easier to write. The alternative
is called a for-each loop. It is probably easiest to
start with an example. Here is a for-each loop for printing all the values
in an array of Strings:

for ( String name : namelist ) {
    System.out.println( name );
}

The meaning of “for (String name : namelist)” is
“for each String, name, in the array, namelist, do the following”. The effect
is that the variable name takes on each of the values in namelist
in turn, and the body of the loop is executed for each of those values. Note that
there is no array index in the loop. The loop control variable, name,
represents one of the values in the array, not the index of one of the values.

The for-each loop is meant specifically for processing all the values
in a data structure, and we will see in Section 7.3 and
Chapter 10 that it applies to other
data structures besides arrays. The for-each loop makes it possible to process the
values in a data structure without even knowing the details of how the data is structured. In the
case of arrays, it lets you avoid the complications of using array indices.

A for-each loop
will perform the same operation for each value that is stored
in an array.
If itemArray is an array of type BaseType[],
then a for-each loop for itemArray has the form:

for ( BaseType item : itemArray ) {
   .
   .  // process the item
   .
}

As usual, the braces are optional if there is only one statement inside the loop.
In this loop, item is the loop control variable. It is
declared as a variable of type BaseType, where
BaseType is the base type of the array. (In a
for-each loop, the loop control variable must be declared in the
loop; it cannot be a variable that already exists outside the loop.)
When this loop is executed, each value from the array is assigned
to item in turn and the body of the loop is executed for each
value. Thus, the above loop is exactly equivalent to:

for ( int index = 0; index < itemArray.length; index++ ) {
   BaseType item;
   item = itemArray[index];  // Get one of the values from the array
     .
     .  // process the item
     .  
}

For example, if A is an array of type int[], then
we could print all the values from A with the for-each loop:

for ( int item : A )
   System.out.println( item );

and we could add up all the positive integers in A with:

int sum = 0;   // This will be the sum of all the positive numbers in A.
for ( int item : A ) {
   if (item > 0)
      sum = sum + item;
}

I also note that the use of var for declaring local variables, which
was introduced in Subsection 4.8.2, applies to the loop control variable
in a for-each loop. So, instead of “for (BaseType item : itemArray)“,
we could write “for (var item : itemArray)“. The
type of the variable is deduced from the base type of the array. This syntax becomes
more useful when dealing with more complicated types.

The for-each loop is not always appropriate. For example, there is no
simple way to use it to process the items in just a part of an array, or to
process the elements in reverse order.
However, it does make the code a little simpler when you do want to process all the values, in order.
since it eliminates any need to use array indices.

But it’s important to note that a for-each loop processes the values in
the array, not the elements (where an element means the actual memory
location that is part of the array and that holds the value). For example, consider the following
incorrect attempt to fill an array of integers with 17’s:

int[] intList = new int[10];
for ( int item : intList ) {   // INCORRECT! DOES NOT MODIFY THE ARRAY!
   item = 17;
}

The assignment statement item = 17 assigns the value 17 to
the loop control variable, item. However, this has nothing to
do with the array. When the body of the loop is executed, the
value from one of the elements of the array is copied into item.
The statement item = 17 replaces that copied value
but has no effect on the array element from which it was copied; the
value in the array is not changed. The loop is equivalent to

int[] intList = new int[10];
for ( int i = 0; i < intList.length; i++ ) {
   int item = intList[i];
   item = 17;
}

which certainly does not change the value of any element in the array.


7.1.2  Variable Arity Methods

Before Java 5, every method in Java had a fixed arity.
(The arity of a method is defined as the number
of parameters in a call to the method.) In a fixed arity method, the number
of parameters must be the same in every call to the method and must be the same
as the number of formal parameters in the method’s definition. Java 5 introduced
variable arity methods. In a variable arity method, different
calls to the method can have different numbers of parameters. For example,
the formatted output method System.out.printf, which was introduced
in Subsection 2.4.1, is a variable arity method. The first
parameter of System.out.printf must be a String,
but it can have any number of additional parameters, of any types.

Calling a variable arity method is no different from calling any other
sort of method, but writing one requires some new syntax. As an example,
consider a method that can compute the average of any number of values of
type double. The definition of such a method could begin
with:

public static double average( double...  numbers ) {

Here, the after the type name, double,
is what makes this a variable arity method. It indicates
that any number of values of type double can be provided when
the subroutine is called, so that for example average(1,4,9,16),
average(3.14,2.17), average(0.375), and even
average() are all legal calls to this method. Note that
actual parameters of type int can be passed to average.
The integers will, as usual, be automatically converted to real numbers.

When the method is called, the values of all the actual parameters
that correspond to the variable arity parameter are placed into an array,
and it is this array that is actually passed to the method. That is,
in the body of a method, a variable arity parameter of type
T actually looks like an ordinary parameter of type
T[]. The length of the array tells you how many actual parameters
were provided in the method call. In the average example, the body
of the method would see an array named numbers of type double[].
The number of actual parameters in the method call would be numbers.length,
and the values of the actual parameters would be numbers[0],
numbers[1], and so on. A complete definition of the method would be:

public static double average( double... numbers ) {
        // Inside this method, numbers is of type double[].
   double sum;      // The sum of all the actual parameters.
   double average;  // The average of all the actual parameters.
   sum = 0;
   for (int i = 0; i < numbers.length; i++) {
      sum = sum + numbers[i];  // Add one of the actual parameters to the sum.
   }
   average = sum / numbers.length;
   return average; // (Will be Double.NaN if numbers.length is zero.)
}

By the way, it is possible to pass a single array
to a variable arity method, instead of a list of individual values. For example, suppose that
salesData is a variable of type double[]. Then
it would be legal to call average(salesData), and this would compute
the average of all the numbers in the array.

The formal parameter list in the definition of a variable-arity method can
include more than one parameter, but the can only be applied to the
very last formal parameter.

As an example, consider a method that can draw a polygon through
any number of points. The points are given as values of type Point,
where an object of type Point has two instance variables,
x and y, of type double.
In this case, the method has one ordinary parameter—the
graphics context that will be used to draw the polygon—in addition to the
variable arity parameter. Remember that inside the definition of the
method, the parameter points becomes an array of
Points:

public static void drawPolygon(GraphicsContext g, Point... points) {
    if (points.length > 1) {  // (Need at least 2 points to draw anything.)
       for (int i = 0; i < points.length - 1; i++) {
           // Draw a line from i-th point to (i+1)-th point
           g.strokeLine( points[i].x, points[i].y, points[i+1].x, points[i+1].y );
       }
       // Now, draw a line back to the starting point.
       g.strokeLine( points[points.length-1].x, points[points.length-1].y,
                       points[0].x, points[0].y );
    }
}

When this method is called, the subroutine call statement must have one actual parameter
of type GraphicsContext,
which can be followed by any number of
actual parameters of type Point.

For a final example, let’s look at a method that strings together all of the
values in a list of strings into a single, long string. This example uses a for-each
loop to process the array:

public static String concat( String... values ) {
   StringBuilder buffer;  // Use StringBuilder for more efficient concatenation.
   buffer = new StringBuilder();  // Start with an empty StringBuilder.
   for ( String str : values ) { // A "for each" loop for processing the values.
       buffer.append(str); // Add string to the buffer.
   }
   return buffer.toString(); // return the contents of the buffer
}

Given this method definition, the method call concat(“Hello”, “World”)
would return the string “HelloWorld”, and concat() would
return an empty string. Since a variable arity method can also accept an
array as actual parameter, we could also call concat(lines)
where lines is of type String[]. This would
concatenate all the elements of the array into a single string.


7.1.3  Array Literals

We have seen that it is possible to initialize an array variable
with a list of values at the time it is declared.
For example,

int[] squares = { 1, 4, 9, 16, 25, 36, 49 };

This initializes squares to refer to a newly created array
that contains the seven values in the list.

A list initializer of this form can be used only in a declaration
statement, where it gives an initial value to a newly declared array variable. It
cannot be used in an assignment statement to assign a value to a variable that
already existed. However, there is another, similar notation for
creating a new array that can be used in other places. The notation uses
another form of the new
operator to both create a new array object and fill it with values.
(The rather odd syntax
is similar to the syntax for anonymous inner classes, which were discussed in
Subsection 5.8.3.) As an example, to assign a new value to
an array variable, cubes, of type int[], you could
use:

cubes = new int[] { 1, 8, 27, 64, 125, 216, 343 };

This is an assignment statement rather than a declaration, so the
array initializer syntax, without “new int[],” would not
be legal here. The general syntax for this form of the
new operator is

new base-type [ ] { list-of-values }

This is actually an expression whose value is a reference to a newly created
array object. In this sense, it is an “array literal,” since it is something that you can type
in a program to represent a value. This means that it can be used in any
context where an object of type base-type
[] is legal.
For example, you could pass the newly created array as an actual parameter to
a subroutine. Consider the following utility method for creating a menu
from an array of strings. (Menus were discussed in
Subsection 6.6.2.)

/**
 * Creates a Menu.  The names for the MenuItems in the menu are
 *     given as an array of Strings.
 * @param menuName  the name for the Menu that is to be created.
 * @param itemNames  an array holding the text that appears in each
 *     MenuItem.  If a null value appears in the array, the corresponding
 *     item in the menu will be a separator rather than a MenuItem.
 * @return  the menu that has been created and filled with items.
 */
public static Menu createMenu( String menuName, String[] itemNames ) {
    Menu menu = new Menu(menuName);
    for ( String itemName : itemNames ) {
        if ( itemName == null ) {
            menu.getItems().add( new SeparatorMenuItem() );
        }
        else {
            MenuItem item = new MenuItem(itemName);
            menu.getItems().add(item);
        }
    }
    return menu;
}

The second parameter in a call to createMenu is an array of
strings. The array that is passed as an actual parameter could be created
in place, using the new operator. For example,
we can use the following statement to create an entire File menu:

Menu fileMenu = createMenu( "File", 
              new String[] { "New", "Open", "Close", null, "Quit" } );

This should convince you that
being able to create and use an array “in place” in this way can be very convenient,
in the same way that anonymous inner classes are convenient. (However, this
example could have been done even more conveniently if createMenu()
had been written as a variable arity method!)

By the way, it is perfectly legal to use the “new BaseType[] { … }” syntax
instead of the array initializer syntax in the declaration of an array variable. For example,
instead of saying:

int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19 };

you can say, equivalently,

int[] primes = new int[] { 2, 3, 5, 7, 11, 17, 19 };

In fact, rather than use a special notation that works only in the context
of declaration statements, I sometimes prefer to use the second form.


One final note: For historical reasons, an array declaration such as

int[] list;

can also be written as

int list[];

which is a syntax used in the languages C and C++. However, this alternative
syntax does not really make much sense in the context of Java, and it is
probably best avoided. After all, the intent is to declare a variable of a
certain type, and the name of that type is “int[]“. It makes sense to
follow the “type-name variable-name;” syntax for such declarations.

License

ITP 220 Advanced Java Copyright © by Amanda Shelton. All Rights Reserved.