Arrays – Declarations

3.9 Arrays

An array is a data structure that defines an indexed collection with a fixed number of data elements that all have the same type. A position in the array is indicated by a non-negative integer value called the index. An element at a given position in the array is accessed using the index. The size of an array is fixed and cannot be changed after the array has been created.

In Java, arrays are objects. Arrays can be of primitive data types or reference types. In the former case, all elements in the array are of a specific primitive data type. In the latter case, all elements are references of a specific reference type. References in the array can then denote objects of this reference type or its subtypes. Each array object has a public final field called length, which specifies the array size (i.e., the number of elements the array can accommodate). The first element is always at index 0 and the last element at index n – 1, where n is the value of the length field in the array.

Simple arrays are one-dimensional arrays—that is, a simple list of values. Since arrays can store reference values, the objects referenced can also be array objects. Thus a multidimensional arrays is implemented as an array of arrays (p. 124).

Passing array references as parameters is discussed in §3.10, p. 127. Type conversions for array references on assignment and on method invocation are discussed in §5.9, p. 261, and §5.10, p. 265, respectively.

Declaring Array Variables

A one-dimensional array variable declaration has either of the following syntaxes:

element_type
[]
array_name
;

or

element_type array_name
[];

where element_type can be a primitive data type or a reference type. The array variable array_name has the type element_type[]. Note that the array size is not specified. As a consequence, the array variable array_name can be assigned the reference value of an array of any length, as long as its elements have element_type.

It is important to understand that the declaration does not actually create an array. Instead, it simply declares a reference that can refer to an array object. The [] notation can also be specified after a variable name to declare it as an array variable, but then it applies to just that variable.

Click here to view code image

int anIntArray[], oneInteger;
Pizza[] mediumPizzas, largePizzas;

These two declarations declare anIntArray and mediumPizzas to be reference variables that can refer to arrays of int values and arrays of Pizza objects, respectively. The variable largePizzas can denote an array of Pizza objects, but the variable oneInteger cannot denote an array of int values—it is a simple variable of the type int.

An array variable that is declared as a field in a class, but is not explicitly initialized to any array, will be initialized to the default reference value null. This default initialization does not apply to local reference variables, and therefore, does not apply to local array variables either. This behavior should not be confused with initialization of the elements of an array during array construction.

Constructing an Array – Declarations

Constructing an Array

An array can be constructed for a fixed number of elements of a specific type, using the new operator. The reference value of the resulting array can be assigned to an array variable of the corresponding type. The syntax of the array creation expression is shown on the right-hand side of the following assignment statement:

Click here to view code image

array_name
 = new
element_type
[
array_size
];

The minimum value of array_size is 0; in other words, zero-length arrays can be constructed in Java. If the array size is negative, a NegativeArraySizeException is thrown at runtime.

Given the declarations

Click here to view code image

int anIntArray[], oneInteger;
Pizza[] mediumPizzas, largePizzas;

the three arrays in the declarations can be constructed as follows:

Click here to view code image

anIntArray   = new int[10];          // array for 10 integers
mediumPizzas = new Pizza[5];         // array of 5 pizzas
largePizzas  = new Pizza[3];         // array of 3 pizzas

The array declaration and construction can be combined.

Click here to view code image

element_type
1
[]
array_name
 = new
element_type
2
[
array_size
];

In the preceding syntax, the array type element_type2[] must be assignable to the array type element_type1[] (§5.8, p. 261). When the array is constructed, all of its elements are initialized to the default value for element_type2. This is true for both member and local arrays when they are constructed.

In the following examples, the code constructs the array, and the array elements are implicitly initialized to their default values. For example, all elements of the array anIntArray get the value 0, and all elements of the array mediumPizzas get the value null when the arrays are constructed.

Click here to view code image

int[] anIntArray = new int[10];                  // Default element value: 0
Pizza[] mediumPizzas = new Pizza[5];             // Default element value: null

The value of the field length in each array is set to the number of elements specified during the construction of the array; for example, mediumPizzas.length has the value 5.

Once an array has been constructed, its elements can also be explicitly initialized individually—for example, in a loop. The examples in the rest of this section make use of a loop to iterate over the elements of an array for various purposes.

Static Members in Classes – Declarations

Static Members in Classes

Static members belong to the class in which they are declared and are not part of any instance of the class. The declaration of static members is prefixed by the keyword static to distinguish them from instance members.

Static code inside a class can access a static member in the following three ways:

  • By the static member’s simple name
  • By using the class name with the static member’s name
  • By using an object reference of the static member’s class with the static member’s name

Depending on the access modifier of the static members declared in a class, clients can only access these members by using the class name or using an object reference of their class.

The class need not be instantiated to access its static members. This is in contrast to instance members of the class which can only be accessed by references that actually refer to an instance of the class.

Static Fields in Classes

Static fields (also called static variables and class variables) exist only in the class in which they are defined. When the class is loaded, static fields are initialized to their default values if no explicit initialization is specified. They are not created when an instance of the class is created. In other words, the values of these fields are not a part of the state of any object. Static fields are akin to global variables that can be shared with all objects of the class and with other clients, if necessary.

Example 3.6 Accessing Static Members in a Class

Click here to view code image

// File: StaticTest.java
import static java.lang.System.out;
class Light {
  // Static field:
  static int counter;                  // (1) No initializer expression
  // Static method:
  public static void printStatic() {
    Light myLight = null;
    out.printf(“%s, %s, %s%n”, counter, Light.counter, myLight.counter); // (2)
long counter = 10;                 // (3) Local variable shadows static field
    out.println(“Local counter: ” + counter);       // (4) Local variable accessed
    out.println(“Static counter: ” + Light.counter);// (5) Static field accessed
//  out.println(this.counter);         // (6) Cannot use this in static context
//  printNonStatic();                  // (7) Cannot call non-static method
  }
  // Non-static method:
  public void printNonStatic() {
   out.printf(“%s, %s, %s%n”, counter, this.counter, Light.counter);     // (8)
  }
}
//______________________________________________________________________________
public class StaticTest {              // Client of class Light
  public static void main(String[] args) {
    Light.counter++;                   // (9) Using class name
    Light dimLight = null;
    dimLight.counter++;                // (10) Using object reference
    out.print(“Light.counter == dimLight.counter: “);
    out.println(Light.counter == dimLight.counter);//(11) Aliases for static field
    out.println(“Calling static method using class name:”);
    Light.printStatic();               // (12) Using class name
    out.println(“Calling static method using object reference:”);
    dimLight.printStatic();            // (13) Using object reference
  }
}

Output from the program:

Click here to view code image

Light.counter == dimLight.counter: true
Calling static method using class name:
2, 2, 2
Local counter: 10
Static counter: 2
Calling static method using object reference:
2, 2, 2
Local counter: 10
Static counter: 2

In Example 3.6, the static field counter at (1) will be initialized to the default value 0 when the class is loaded at runtime, since no initializer expression is specified. The print statement at (2) in the static method printCount() shows how this static field can be accessed in three different ways, respectively: simple name counter, the class name Light, and object reference myLight of class Light, although no object has been created.

Shadowing of fields by local variables is different from hiding of fields by field declarations in subclasses. In Example 3.6, a local variable is declared at (3) that has the same name as the static field. Since this local variable shadows the static field, the simple name at (4) now refers to the local variable, as shown by the output from the program. The shadowed static field can of course be accessed using the class name, as shown at (5). It is the local variable that is accessed by its simple name as long as it is in scope.

Trying to access the static field with the this reference at (6) results in a compile-time error, since the this reference cannot be used in static code. Invoking the non-static method at (7) also results in a compile-time error, since static code cannot refer to non-static members by its simple name in the class.

The print statement at (8) in the method printNonStatic() illustrates referring to static members in non-static code: It refers to the static field counter by its simple name, with the this reference, and using the class name.

In Example 3.6, the class StaticTest is a client of the class Light. The client must use the class name or an object reference of class Light at (9) and (10), respectively, to access the static field counter in the class Light. The result from the print statement at (11) shows that these two ways of accessing a static field are equivalent.

The switch Statement with the Arrow (->) Notation – Control Flow

The switch Statement with the Arrow (->) Notation

The form of the switch statement with the arrow notation is shown in Figure 4.4. This form defines switch rules in which each case label is associated with a corresponding action using the arrow (->) notation.

Figure 4.4 Form of the switch Statement with the Arrow Notation

switch (
selector_expression
) {
  // Switch block with switch rules defined using arrow notation:
  case
CC
                  ->
expression_statement
;
  case
CC
1
,
CC
2
, …,
CC
m
  ->
block

  case
CC
4
                 ->
throw_statement

  …
  default                  -> …
}

Compared to the switch statement with the colon notation (Figure 4.2), there are a few things to note.

First, although the case labels (and the default label) are specified similarly, the arrow notation does not allow multiple case labels to be associated with a common action. However, the same result can be achieved by specifying a single case label with a list of case constants, thereby associating the case constants with a common action.

Second, the action that can be associated with the case labels in switch rules is restricted. The switch statement with the colon notation allows a group of statements, but the switch statement with the arrow notation only allows the following actions to be associated with case labels:

By far, the canonical action of a case label in a switch rule is an expression statement. Such an expression statement is always terminated by a semicolon (;). Typically, the value returned by the expression statement is discarded. In the examples below, what is important is the side effect of evaluating the expression statements.

Click here to view code image


case PASSED -> ++numbersPassed;
case FAILED -> ++numbersFailed;

A block of statements can be used if program logic should be refined.

Click here to view code image


case ALARM ->  { soundTheAlarm();
                 callTheFireDepartment(); }

The switch rule below throws an exception when the value of the selector expression does not match any case constants:

Click here to view code image


default -> throw new IllegalArgumentException(“Not a valid value”);

Third, the execution of the switch rules is mutually exclusive (Figure 4.5). Once the action in the switch rule has completed execution, the execution of the switch statement terminates. This is illustrated in Figure 4.5 where only one expression statement is executed, after which the switch statement also terminates. There is no fall-through and the break statement is not necessary.

Figure 4.5 Activity Diagram for the switch Statement with the Arrow Notation

Example 4.3 is a refactoring of Example 4.2 with a switch statement with the arrow notation. At (2), (3), (4), and (8), the action executed is an expression statement, whereas at (5), the action executed is a block. Using switch rules results in compact and elegant code that also improves the readability of the switch statement.

Example 4.3 Nested switch Statements with the Arrow Notation

Click here to view code image

public class SeasonsII {
  public static void main(String[] args) {
    int monthNumber = 11;
    switch(monthNumber) {                                             // (1) Outer
      case 12, 1,  2 -> System.out.println(“Snow in the winter.”);    // (2)
      case 3,  4,  5 -> System.out.println(“Green grass in the spring.”);   // (3)
      case 6,  7,  8 -> System.out.println(“Sunshine in the summer.”);      // (4)
      case 9, 10, 11 -> {                                             // (5)
        switch(monthNumber) { // Nested switch                           (6) Inner
          case 10 -> System.out.println(“Halloween.”);
          case 11 -> System.out.println(“Thanksgiving.”);
        }
        // Always printed for case constants 9, 10, 11:
        System.out.println(“Yellow leaves in the fall.”);             // (7)
      }
      default -> throw new IllegalArgumentException(monthNumber +
                                            ” is not a valid month.”);// (8)
    }
  }
}

Output from the program:

Click here to view code image

Thanksgiving.
Yellow leaves in the fall.

Multidimensional Arrays – Declarations

Multidimensional Arrays

Since an array element can be an object reference and arrays are objects, array elements can themselves refer to other arrays. In Java, an array of arrays can be defined as follows:

Click here to view code image

element_type
[][]…[]
array_name;

or

Click here to view code image

element_type array_name
[][]…[];

In fact, the sequence of square bracket pairs, [], indicating the number of dimensions, can be distributed as a postfix to both the element type and the array name. Arrays of arrays are often called multidimensional arrays.

The following declarations are all equivalent:

Click here to view code image

int[][] mXnArray;      // two-dimensional array
int[]   mXnArray[];    // two-dimensional array
int     mXnArray[][];  // two-dimensional array

It is customary to combine the declaration with the construction of the multidimensional array.

Click here to view code image

int[][] mXnArray = new int[4][5];    // 4 x 5 matrix of ints

The previous declaration constructs an array mXnArray of four elements, where each element is an array (row) of five int values. The concept of rows and columns is often used to describe the dimensions of a two-dimensional array, which is often called a matrix. However, such an interpretation is not dictated by the Java language.

Each row in the previous matrix is denoted by mXnArray[i], where 0 ≤ i < 4. Each element in the ith row, mXnArray[i], is accessed by mXnArray[i][j], where 0 ≤ j < 5. The number of rows is given by mXnArray.length, in this case 4, and the number of values in the ith row is given by mXnArray[i].length, in this case 5 for all the rows, where 0 ≤ i < 4.

Multidimensional arrays can also be constructed and explicitly initialized using the array initializers discussed for simple arrays. Note that each row is an array that uses an array initializer to specify its values:

Click here to view code image

double[][] identityMatrix = {
  {1.0, 0.0, 0.0, 0.0 }, // 1. row
  {0.0, 1.0, 0.0, 0.0 }, // 2. row
  {0.0, 0.0, 1.0, 0.0 }, // 3. row
  {0.0, 0.0, 0.0, 1.0 }  // 4. row
}; // 4 x 4 floating-point matrix

Arrays in a multidimensional array need not have the same length; in which case, they are called ragged arrays. The array of arrays pizzaGalore in the following code has five rows; the first four rows have different lengths but the fifth row is left unconstructed:

Click here to view code image

Pizza[][] pizzaGalore = {
  { new Pizza(), null, new Pizza() },    // 1. row is an array of 3 elements.
  { null, new Pizza()},                  // 2. row is an array of 2 elements.
  new Pizza[1],                          // 3. row is an array of 1 element.
  {},                                    // 4. row is an array of 0 elements.
  null                                   // 5. row is not constructed.
};

When constructing multidimensional arrays with the new operator, the length of the deeply nested arrays may be omitted. In such a case, these arrays are left unconstructed. For example, an array of arrays to represent a room (defined by class HotelRoom) on a floor in a hotel on a street in a city can have the type HotelRoom[][][][]. From left to right, the square brackets represent indices for street, hotel, floor, and room, respectively. This four-dimensional array of arrays can be constructed piecemeal, starting with the leftmost dimension and proceeding to the rightmost successively.

Click here to view code image

HotelRoom[][][][] rooms = new HotelRoom[10][5][][];  // Just streets and hotels.

The preceding declaration constructs the array of arrays rooms partially with 10 streets, where each street has five hotels. Floors and rooms can be added to a particular hotel on a particular street:

Click here to view code image

rooms[0][0]       = new HotelRoom[3][]; // 3 floors in 1st hotel on 1st street.
rooms[0][0][0]    = new HotelRoom[8];   // 8 rooms on 1st floor in this hotel.
rooms[0][0][0][0] = new HotelRoom();    // Initializes 1st room on this floor.

The next code snippet constructs an array of arrays matrix, where the first row has one element, the second row has two elements, and the third row has three elements. Note that the outer array is constructed first. The second dimension is constructed in a loop that constructs the array in each row. The elements in the multidimensional array will be implicitly initialized to the default double value (0.0D). In Figure 3.1, the array of arrays matrix is depicted after the elements have been explicitly initialized.

Click here to view code image

double[][] matrix = new double[3][];      // (1) Number of rows.

for (int i = 0; i < matrix.length; ++i)
  matrix[i] = new double[i + 1];          // Construct a row.

Figure 3.1 Array of Arrays

The type of the variable matrix is double[][] at (1), a two-dimensional array of double values. The type of the variable matrix[i] (where 0 ≤ i< matrix.length) is double[], a one-dimensional array of double values. The type of the variable matrix[i][j] (where 0 ≤ i< matrix.length and 0 ≤ j< matrix[i].length) is double, a simple variable of type double.

Two other ways of initializing such an array of arrays are shown next. The first approach uses array initializers, and the second uses an anonymous array of arrays.

Click here to view code image

double[][] matrix2 = {    // (2) Using array initializers.
  {1.0},                  // 1. row
  {1.0, 2.0},             // 2. row
  {1.0, 2.0, 3.0}         // 3. row
};

double[][] matrix3 = new double[][] { // (3) Using an anonymous array of arrays.
  {1.0},                  // 1. row
  {1.0, 2.0},             // 2. row
  {1.0, 2.0, 1.0}         // 3. row
};

Nested loops are a natural match for manipulating multidimensional arrays. In Example 3.9, a rectangular 4 × 3 int matrix is declared and constructed at (1). The program finds the minimum value in the matrix. The outer loop at (2) iterates over the rows (mXnArray[i], where 0 ≤ i< mXnArray.length), and the inner loop at (3) iterates over the elements in each row in turn (mXnArray[i][j], where 0 ≤ j< mXnArray[i].length). The outer loop is executed mXnArray.length times, or four times, and the inner loop is executed (mXnArray.length) × (mXnArray[i].length), or 12 times, since all rows have the same length 3.

The for(:) loop also provides a safe and convenient way of iterating over an array. Several examples of its use are provided in §4.8, p. 176.

Example 3.9 Using Multidimensional Arrays

Click here to view code image

public class MultiArrays {
  public static void main(String[] args) {
    // Declare and construct the M X N matrix.
    int[][] mXnArray = {                                           // (1)
        {16,  7, 12}, // 1. row
        { 9, 20, 18}, // 2. row
        {14, 11,  5}, // 3. row
        { 8,  5, 10}  // 4. row
    }; // 4 x 3 int matrix
    // Find the minimum value in an M X N matrix:
    int min = mXnArray[0][0];
    for (int i = 0; i < mXnArray.length; ++i)                      // (2)
      // Find min in mXnArray[i], in the row given by index i:
      for (int j = 0; j < mXnArray[i].length; ++j)                 // (3)
        min = Math.min(min, mXnArray[i][j]);
    System.out.println(“Minimum value: ” + min);
  }
}”

Output from the program: Minimum value: 5

Passing Primitive Data Values – Declarations

Passing Primitive Data Values

An actual parameter is an expression that is evaluated first, with the resulting value then being assigned to the corresponding formal parameter at method invocation. The use of this value in the method has no influence on the actual parameter. In particular, when the actual parameter is a variable of a primitive data type, the value of the variable from the stack is copied to the formal parameter at method invocation. Since formal parameters are local to the method, any changes made to the formal parameter will not be reflected in the actual parameter after the call completes.

Legal type conversions between actual parameters and formal parameters of primitive data types are summarized here from Table 2.17, p. 47:

  • Primitive widening conversion
  • Unboxing conversion, followed by an optional widening primitive conversion

These conversions are illustrated by invoking the following method

Click here to view code image

static void doIt(long i) { /* … */ }

with the following code:

Click here to view code image

Integer intRef = 34;
Long longRef = 34L;
doIt(34);         // (1) Primitive widening conversion: long <– int
doIt(longRef);    // (2) Unboxing: long <– Long
doIt(intRef);     // (3) Unboxing, followed by primitive widening conversion:
                  //     long <– int <– Integer

However, for parameter passing, there are no implicit narrowing conversions for integer constant expressions (§2.4, p. 48).

Example 3.10 Passing Primitive Values

Click here to view code image

public class CustomerOne {
  public static void main (String[] args) {
    PizzaFactory pizzaHouse = new PizzaFactory();
    int pricePrPizza = 15;
    System.out.println(“Value of pricePrPizza before call: ” + pricePrPizza);
    double totPrice = pizzaHouse.calcPrice(4, pricePrPizza);             // (1)
    System.out.println(“Value of pricePrPizza after call: ” + pricePrPizza);
  }
}
class PizzaFactory {
  public double calcPrice(int numberOfPizzas, double pizzaPrice) {       // (2)
    pizzaPrice = pizzaPrice / 2.0;       // Changes price.
    System.out.println(“Changed pizza price in the method: ” + pizzaPrice);
    return numberOfPizzas * pizzaPrice;
  }
}

Output from the program:

Click here to view code image

Value of pricePrPizza before call: 15
Changed pizza price in the method: 7.5
Value of pricePrPizza after call: 15

In Example 3.10, the method calcPrice() is defined in the class PizzaFactory at (2). It is called from the CustomerOne.main() method at (1). The value of the first actual parameter, 4, is copied to the int formal parameter numberOfPizzas. Note that the second actual parameter pricePrPizza is of the type int, while the corresponding formal parameter pizzaPrice is of the type double. Before the value of the actual parameter pricePrPizza is copied to the formal parameter pizzaPrice, it is implicitly widened to a double. The passing of primitive values is illustrated in Figure 3.2.

Figure 3.2 Parameter Passing: Primitive Data Values

The value of the formal parameter pizzaPrice is changed in the calcPrice() method, but this does not affect the value of the actual parameter pricePrPizza on return. It still has the value 15. The bottom line is that the formal parameter is a local variable, and changing its value does not affect the value of the actual parameter.

Using an Array – Declarations

Using an Array

The array object is referenced by the array name, but individual array elements are accessed by specifying an index with the [] operator. The array element access expression has the following syntax:

Click here to view code image

array_name
 [
index_expression
]

Each individual element is treated as a simple variable of the element type. The index is specified by the index_expression, whose value should be promotable to an int value; otherwise, a compile-time error is flagged. Since the lower bound of an array index is always 0, the upper bound is 1 less than the array size—that is, array_name.length-1. The ith element in the array has index (i-1). At runtime, the index value is automatically checked to ensure that it is within the array index bounds. If the index value is less than 0, or greater than or equal to array_name.length, an ArrayIndexOutOfBoundsException is thrown. A program can either explicitly check that the index value is within the array index bounds or catch the runtime exception that is thrown if it is invalid (§7.3, p. 375), but an illegal index is typically an indication of a programming error.

In the array element access expression, the array_name can be any expression that returns a reference to an array. For example, the expression on the right-hand side of the following assignment statement returns the character ‘H’ at index 1 in the character array returned by a call to the toCharArray() method of the String class:

Click here to view code image

char letter = “AHA”.toCharArray()[1];     // ‘H’

The array operator [] is used to declare array types (Topping[]), specify the array size (new Topping[3]), and access array elements (toppings[1]). This operator is not used when the array reference is manipulated, such as in an array reference assignment, or when the array reference is passed as an actual parameter in a method call (p. 132).

Example 3.7 shows traversal of arrays using for loops (§4.7, p. 174 and p. 176). A for(;;) loop at (3) in the main() method initializes the local array trialArray declared at (2) five times with pseudorandom numbers (from 0.0 to 100.0), by calling the method randomize() declared at (5). The minimum value in the array is found by calling the method findMinimum() declared at (6), and is stored in the array storeMinimum declared at (1). Both of these methods also use a for(;;) loop. The loop variable is initialized to a start value—0 at (3) and (5), and 1 at (6). The loop condition tests whether the loop variable is less than the length of the array; this guarantees that the loop will terminate when the last element has been accessed. The loop variable is incremented after each iteration to access the next element.

A for(:) loop at (4) in the main() method is used to print the minimum values from the trials, as elements are read consecutively from the array, without keeping track of an index value.

Example 3.7 Using Arrays

Click here to view code image

public class Trials {
  public static void main(String[] args) {
    // Declare and construct the local arrays:
    double[] storeMinimum = new double[5];               // (1)
    double[] trialArray = new double[15];                // (2)
    for (int i = 0; i < storeMinimum.length; ++i) {      // (3)
      // Initialize the array.
      randomize(trialArray);

      // Find and store the minimum value.
      storeMinimum[i] = findMinimum(trialArray);
    }

    // Print the minimum values:                            (4)
    for (double minValue : storeMinimum)
      System.out.printf(“%.4f%n”, minValue);
  }

  public static void randomize(double[] valArray) {      // (5)
    for (int i = 0; i < valArray.length; ++i)
      valArray[i] = Math.random() * 100.0;
  }

  public static double findMinimum(double[] valArray) {  // (6)
    // Assume the array has at least one element.
    double minValue = valArray[0];
    for (int i = 1; i < valArray.length; ++i)
      minValue = Math.min(minValue, valArray[i]);
    return minValue;
  }
}

Probable output from the program: 6.9330
2.7819
6.7427
18.0849
26.2462

Passing Arrays – Declarations

Passing Arrays

The discussion of passing reference values in the previous section is equally valid for arrays, as arrays are objects in Java. Method invocation conversions for array types are discussed along with those for other reference types in §5.10, p. 265.

In Example 3.12, the idea is to repeatedly swap neighboring elements in an integer array until the largest element in the array percolates to the last position in the array.

Example 3.12 Passing Arrays

Click here to view code image

public class Percolate {
  public static void main (String[] args) {
    int[] dataSeq = {8,4,6,2,1};    // Create and initialize an array.
    // Write array before percolation:
    printIntArray(dataSeq);
    // Percolate:
    for (int index = 1; index < dataSeq.length; ++index)
      if (dataSeq[index-1] > dataSeq[index])
        swap(dataSeq, index-1, index);                    // (1)
    // Write array after percolation:
    printIntArray(dataSeq);
  }
  public static void swap(int[] intArray, int i, int j) { // (2)
    int tmp = intArray[i]; intArray[i] = intArray[j]; intArray[j] = tmp;
  }
  public static void swap(int v1, int v2) {               // (3) Logical error!
    int tmp = v1; v1 = v2; v2 = tmp;
  }
  public static void printIntArray(int[] array) {         // (4)
    for (int value : array)
      System.out.print(” ” + value);
    System.out.println();
  }
}

Output from the program:

8 4 6 2 1
4 6 2 1 8

Note that in the declaration of the method swap() at (2), the formal parameter intArray is of the array type int[]. The other two parameters are of type int. They denote the values in the array that should be swapped. The signature of the method is

swap(int[], int, int)

This swap() method is called in the main() method at (1), where one of the actual parameters is the array variable dataSeq. The reference value of the array variable dataSeq is assigned to the array variable intArray at method invocation. After return from the call to the swap() method, the array variable dataSeq will reflect the changes made to the array via the corresponding formal parameter. This situation is depicted in Figure 3.4 at the first call and return from the swap() method, indicating how the values of the elements at indices 0 and 1 in the array have been swapped.

Figure 3.4 Parameter Passing: Arrays

However, the declaration of the swap() method at (3) will not swap two values. The method call

Click here to view code image

swap(dataSeq[index-1], dataSeq[index]); // Call signature: swap(int, int)

will result in the swap() method at (3) to be invoked. Its execution will have no effect on the array elements, as the swapping is done on the values of the formal parameters.

The method printIntArray() at (4) also has a formal parameter of array type int[]. Note that the formal parameter is specified as an array reference using the [] notation, but this notation is not used when an array is passed as an actual parameter.

Variable Arity Methods – Declarations

3.11 Variable Arity Methods

A fixed arity method must be called with the same number of actual parameters (also called arguments) as the number of formal parameters specified in its declaration. If the method declaration specifies two formal parameters, every call of this method must specify exactly two arguments. We say that the arity of this method is 2. In other words, the arity of such a method is fixed, and it is equal to the number of formal parameters specified in the method declaration.

Java also allows declaration of variable arity methods (also called varargs methods), meaning that the number of arguments in its call can be varied. As we shall see, invocations of such a method may contain more actual parameters than formal parameters. Variable arity methods are heavily employed in formatting text representation of values, as demonstrated by the variable arity method System.out.printf() that is used in many examples for this purpose.

The last formal parameter in a variable arity method declaration is declared as follows:

Click here to view code image

type

formal_parameter_name

The ellipsis (…) is specified between the type and the formal_parameter_name. The type can be a primitive type, a reference type, or a type parameter. Whitespace can be specified on both sides of the ellipsis. Such a parameter is usually called a variable arity parameter (also known as a varargs parameter).

Apart from the variable arity parameter, a variable arity method is identical to a fixed arity method. The method publish() below is a variable arity method:

Click here to view code image

public static void publish(int n, String… data) {      // (int, String[])
  System.out.println(“n: ” + n + “, data size: ” + data.length);
}

The variable arity parameter in a variable arity method is always interpreted as having an array type:

type
[]

In the body of the publish() method, the variable arity parameter data has the type String[], so it is a simple array of Strings.

Only one variable arity parameter is permitted in the formal parameter list, and it is always the last parameter in the list. Given that the method declaration has n formal parameters and the method call has k actual parameters, k must be equal to or greater than n – 1. The last k – n + 1 actual parameters are evaluated and stored in an array whose reference value is passed as the value of the actual parameter. In the case of the publish() method, n is equal to 2, so k can be 1, 2, 3, and so on. The following invocations of the publish() method show which arguments are passed in each method call:

Click here to view code image

publish(1);                  // (1, new String[] {})
publish(2, “two”);           // (2, new String[] {“two”})
publish(3, “two”, “three”);  // (3, new String[] {“two”, “three”})

Each method call results in an implicit array being created and passed as an argument. This array can contain zero or more argument values that do not correspond to the formal parameters preceding the variable arity parameter. This array is referenced by the variable arity parameter data in the method declaration. The preceding calls would result in the publish() method printing the following output:

n: 1, data size: 0
n: 2, data size: 1
n: 3, data size: 2

To overload a variable arity method, it is not enough to change the type of the variable arity parameter to an explicit array type. The compiler will complain if an attempt is made to overload the method transmit(), as shown in the following code:

Click here to view code image

public static void transmit(String… data) {  }  // Compile-time error!
public static void transmit(String[] data)  {  }  // Compile-time error!

Both methods above have the signature transmit(String[]). These declarations would result in two methods with equivalent signatures in the same class, which is not permitted.

Overloading and overriding of methods with variable arity are discussed in §5.10, p. 265.

Array Elements as Actual Parameters – Declarations

Array Elements as Actual Parameters

Array elements, like other variables, can store values of primitive data types or reference values of objects. In the latter case, they can also be arrays—that is, arrays of arrays (p. 124). If an array element is of a primitive data type, its data value is passed; if it is a reference to an object, the reference value is passed. The method invocation conversions apply to the values of array elements as well.

Example 3.13 Array Elements as Primitive Data Values

Click here to view code image

public class FindMinimum {
  public static void main(String[] args) {
    int[] dataSeq = {6,4,8,2,1};
    int minValue = dataSeq[0];
    for (int index = 1; index < dataSeq.length; ++index)
      minValue = minimum(minValue, dataSeq[index]);            // (1)
    System.out.println(“Minimum value: ” + minValue);
  }
  public static int minimum(int i, int j) {                    // (2)
    return (i <= j) ? i : j;
  }
}

Output from the program:

Minimum value: 1

In Example 3.13, the value of all but one element of the array dataSeq is retrieved and passed consecutively at (1) to the formal parameter j of the minimum() method defined at (2). The discussion on passing primitive values (p. 129) also applies to array elements that have primitive values.

In Example 3.14, the formal parameter seq of the findMinimum() method defined at (4) is an array variable. The variable matrix denotes an array of arrays declared at (1) simulating a multidimensional array that has three rows, where each row is a simple array. The first row, denoted by matrix[0], is passed to the findMinimum() method in the call at (2). Each remaining row is passed by its reference value in the call to the findMinimum() method at (3).

Example 3.14 Array Elements as Reference Values

Click here to view code image

public class FindMinimumMxN {
  public static void main(String[] args) {
    int[][] matrix = { {8,4},{6,3,2},{7} };                  // (1)
    int min = findMinimum(matrix[0]);                        // (2)
    for (int i = 1; i < matrix.length; ++i) {
      int minInRow = findMinimum(matrix[i]);                 // (3)
      min = Math.min(min, minInRow);
    }
    System.out.println(“Minimum value in matrix: ” + min);
  }
  public static int findMinimum(int[] seq) {                 // (4)
    int min = seq[0];
    for (int i = 1; i < seq.length; ++i)
      min = Math.min(min, seq[i]);
    return min;
  }
}

Output from the program:

Minimum value in matrix: 2

final Parameters

A formal parameter can be declared with the keyword final preceding the parameter declaration in the method declaration. A final parameter is also known as a blank final variable; that is, it is blank (uninitialized) until a value is assigned to it, (e.g., at method invocation) and then the value in the variable cannot be changed during the lifetime of the variable (see also the discussion in §6.6, p. 352). The compiler can treat final variables as constants for code optimization purposes. Declaring parameters as final prevents their values from being changed inadvertently. A formal parameter’s declaration as final does not affect the caller’s code.

The declaration of the method calcPrice() from Example 3.10 is shown next, with the formal parameter pizzaPrice declared as final:

Click here to view code image

public double calcPrice(int numberOfPizzas, final double pizzaPrice) {  // (2′)
  pizzaPrice = pizzaPrice/2.0;        // (3) Not allowed. Compile-time error!
  return numberOfPizzas * pizzaPrice;
}

If this declaration of the calcPrice() method is compiled, the compiler will not allow the value of the final parameter pizzaPrice to be changed at (3) in the body of the method.

As another example, the declaration of the method bake() from Example 3.11 is shown here, with the formal parameter pizzaToBeBaked declared as final:

Click here to view code image

public static void bake(final Pizza pizzaToBeBaked) { // (3)
  pizzaToBeBaked.meat = “chicken”;   // (3a) Allowed
  pizzaToBeBaked = null;             // (4) Not allowed. Compile-time error!
}

If this declaration of the bake() method is compiled, the compiler will not allow the reference value of the final parameter pizzaToBeBaked to be changed at (4) in the body of the method. Note that this applies to the reference value in the final parameter, but not to the object denoted by this parameter. The state of the object can be changed as before, as shown at (3a).

For use of the final keyword in other contexts, see §5.5, p. 225.