Like methods, constructors can be overloaded. Since the constructors in a class all have the same name as the class, their signatures are differentiated by their parameter lists. In the following example, the class Light now provides explicit implementation of the no-argument constructor at (1) and that of a non-zero argument constructor at (2). The constructors are overloaded, as is evident by their signatures. The non-zero argument constructor at (2) is called when an object of the class Light is created at (4), and the no-argument constructor is likewise called at (3). Overloading of constructors allows appropriate initialization of objects on creation, depending on the constructor invoked (see chaining of constructors in §5.3, p. 209). It is recommended to use the @param tag in a Javadoc comment to document the formal parameters of a constructor.
The scope of a local variable declared in a switch statement or a switch expression is the entire switch block. Any local block in the switch body introduces a new local scope. Any local variable declared in it has block scope, and therefore, is only accessible in that block. A local variable declared in an enclosing local scope cannot be redeclared in a nested local scope (§6.6, p. 354).
Summary of the switch Statement and the switch Expression
Table 4.1 summarizes the features of the switch statement and the switch expression, and provides a comparison of the two constructs.
Table 4.1 Comparing the switch Statement and the switch Expression
Notation
The switch statement
The switch expression
The colon (:) notation: case label: statements
Executes statements associated with the matching case label.Fall-through can occur.No compile-time check for exhaustiveness.Only break and return statements allowed to control fall-through.
Executes statements associated with the matching case label, but must have a yield statement to return a value.Fall-through can occur.Compile-time check for exhaustiveness.No break or return statement allowed.
The arrow (->) notation: case label -> action
Action associated with a switch rule can be an expression statement, can be a block, or can throw an exception.Mutually exclusive switch rules: no fall-through can occur.No compile-time check for exhaustiveness.break and return statements allowed.
Action associated with a switch rule can be any expression, can be a block, or can throw an exception.Mutually exclusive switch rules: no fall-through can occur.Compile-time check for exhaustiveness.No break or return statement allowed.Must return a value that is either the value of a stand-alone expression or the value of the expression in a yield statement that can occur as the last statement in a block.
The size of the array is specified in the array creation expression, which creates the array and initializes the array elements to their default values. By comparison, the following declaration statement both creates the array and initializes the array elements to specific values given in the array initializer:
However, the array initializer is not an expression. Java has another array creation expression, called an anonymous array, which allows the concept of the array creation expression from (1) to be combined with the array initializer from (2), so as to create and initialize an array:
This construct has enough information to create a nameless array of a specific type and specific length. Neither the name of the array nor the size of the array is specified. The construct returns the reference value of the newly created array, which can be assigned to references and passed as arguments in method calls. In particular, the following declaration statements are equivalent:
At (1), an array initializer is used to create and initialize the elements. At (2), an anonymous array expression is used. It is tempting to use the array initializer as an expression—for example, in an assignment statement, as a shortcut for assigning values to array elements in one go. However, this is not allowed; instead, an anonymous array expression should be used. The concept of the anonymous array combines the definition and the creation of the array into one operation.
In Example 3.8, an anonymous array is constructed at (1), and passed as an actual parameter to the static method findMinimum() defined at (2). Note that no array name or array size is specified for the anonymous array.
public class AnonArray { public static void main(String[] args) { System.out.println(“Minimum value: ” + findMinimum(new int[] {3, 5, 2, 8, 6})); // (1) } public static int findMinimum(int[] dataSeq) { // (2) // Assume the array has at least one element. int min = dataSeq[0]; for (int index = 1; index < dataSeq.length; ++index) if (dataSeq[index] < min) min = dataSeq[index]; return min; } }
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:
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.
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.
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.
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.
… 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
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) } } }
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:
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.
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:
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:
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.
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:
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.
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.
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.
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); } }”
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
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).
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.
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
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).
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:
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:
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.
Java provides selection statements that allow the program to choose between alternative actions during execution. The choice is based on criteria specified in the selection statement. These selection statements are
The simple if statement
The if-else statement
The switch statement and the switch expression
The Simple if Statement
The simple if statement has the following syntax:
if ( condition ) statement
It is used to decide whether an action is to be performed or not, based on a condition. The action to be performed is specified by statement, which can be a single statement or a code block. The condition must evaluate to a boolean or Boolean value. In the latter case, the Boolean value is unboxed to the corresponding boolean value.
The semantics of the simple if statement are straightforward. The condition is evaluated first. If its value is true, statement (called the if block) is executed and then execution continues with the rest of the program. If the value is false, the if block is skipped and execution continues with the rest of the program. The semantics are illustrated by the activity diagram in Figure 4.1a.
Figure 4.1 Activity Diagram for if Statements
In the following examples of the if statement, it is assumed that the variables and the methods have been appropriately defined:
if (emergency) // emergency is a boolean variable operate(); if (temperature > critical) soundAlarm(); if (isLeapYear() && endOfCentury()) celebrate(); if (catIsAway()) { // Block getFishingRod(); goFishing(); }
Note that statement can be a block, and the block notation is necessary if more than one statement is to be executed when the condition is true.
Since the condition evaluates to a boolean value, it avoids a common programming error: using an expression of the form (a=b) as the condition, where inadvertently an assignment operator is used instead of a relational operator. The compiler will flag this as an error, unless both a and b are boolean.
Note that the if block can be any valid statement. In particular, it can be the empty statement (;) or the empty block ({}). A common programming error is inadvertent use of the empty statement.
Example 3.15 illustrates various aspects of calling a variable arity method. The method flexiPrint() in the VarargsDemo class has a variable arity parameter:
public static void flexiPrint(Object… data) { // Object[] //… }
The variable arity method prints the name of the Class object representing the actual array that is passed at runtime. It prints the number of elements in this array as well as the text representation of each element in the array.
The method flexiPrint() is called in the main() method. First it is called with the values of primitive types and Strings ((1) to (8)), and then it is called with the program arguments (p. 141) supplied on the command line ((9) to (11)).
Compiling the program results in a warning at (9), which we ignore for the time being. The program can still be run, as shown in Example 3.15. The numbers at the end of the lines in the output relate to numbers in the code, and are not printed by the program.
> javac VarargsDemo.java VarargsDemo.java:41: warning: non-varargs call of varargs method with inexact argument type for last parameter; flexiPrint(args); // (9) Warning! ^ cast to Object for a varargs call cast to Object[] for a non-varargs call and to suppress this warning 1 warning
Type: [Ljava.lang.Object; No. of elements: 0 (1) Element values: Type: [Ljava.lang.Object; No. of elements: 1 (2) Element values: 13 Type: [Ljava.lang.Object; No. of elements: 2 (3) Element values: 13 August Type: [Ljava.lang.Object; No. of elements: 3 (4) Element values: 13 August 2009 Type: [Ljava.lang.Object; No. of elements: 3 (6) Element values: 13 August 2009 Type: [Ljava.lang.Object; No. of elements: 1 (7) Element values: [Ljava.lang.Object;@1eed786 Type: [Ljava.lang.Object; No. of elements: 1 (8) Element values: [Ljava.lang.Object;@1eed786 Type: [Ljava.lang.String; No. of elements: 6 (9) Element values: To arg or not to arg Type: [Ljava.lang.Object; No. of elements: 1 (10) Element values: [Ljava.lang.String;@187aeca Type: [Ljava.lang.String; No. of elements: 6 (11) Element values: To arg or not to arg