Overloaded Constructors – Declarations

Overloaded Constructors

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.

Click here to view code image

class Light {
  // …
  // No-argument constructor:
  Light() {                                                  // (1)
    noOfWatts = 50;
    indicator = true;
    location  = “X”;
  }
  // Non-zero argument constructor:
  Light(int noOfWatts, boolean indicator, String location) { // (2)
    this.noOfWatts = noOfWatts;
    this.indicator = indicator;
    this.location  = location;
  }
  //…
}
class Greenhouse {
  // …
  Light firstLight = new Light();                        // (3) OK. Calls (1)
  Light moreLight  = new Light(100, true, “Greenhouse”); // (4) OK. Calls (2)
}

3.8 Static Member Declarations

In this section we look at static members in classes, but in general, the keyword static is used in the following contexts:

Iteration Statements – Control Flow

4.4 Iteration Statements

Loops allow a single statement or a statement block to be executed repeatedly (i.e., iterated). A boolean condition (called the loop condition) is commonly used to determine when to terminate the loop. The statements executed in the loop constitute the loop body.

Java provides four language constructs for loop construction:

  • The while statement
  • The do-while statement
  • The basic for statement
  • The enhanced for statement

These loops differ in the order in which they execute the loop body and test the loop condition. The while loop and the basic for loop test the loop condition before executing the loop body, whereas the do-while loop tests the loop condition after execution of the loop body.

The enhanced for loop (also called the for-each loop) simplifies iterating over arrays and collections. We will use the notations for(;;) and for(:) to designate the basic for loop and the enhanced for loop, respectively.

4.5 The while Statement

The syntax of the while loop is

while (
loop_condition
)
loop_body

The loop condition is evaluated before executing the loop body. The while statement executes the loop body as long as the loop condition is true. When the loop condition becomes false, the loop is terminated and execution continues with any statement immediately following the loop. If the loop condition is false to begin with, the loop body is not executed at all. In other words, a while loop can execute zero or more times. The loop condition must evaluate to a boolean or a Boolean value. In the latter case, the reference value is unboxed to a boolean value. The flow of control in a while statement is shown in Figure 4.6.

Figure 4.6 Activity Diagram for the while Statement

The while statement is normally used when the number of iterations is not known.

while (noSignOfLife())
  keepLooking();

Since the loop body can be any valid statement, inadvertently terminating each line with the empty statement (;) can give unintended results. Always using a block statement as the loop body helps to avoid such problems.

Click here to view code image

while (noSignOfLife());     // Empty statement as loop body!
  keepLooking();            // Statement not in the loop body.

Initializing an Array – Declarations

Initializing an Array

Java provides the means to declare, construct, and explicitly initialize an array in one declaration statement:

Click here to view code image

element_type
[]
array_name
 = {
array_initialize_list
 };

This form of initialization applies to fields as well as to local arrays. The array_initialize_list is a comma-separated list of zero or more expressions. Such an array initializer results in the construction and initialization of the array.

Click here to view code image

int[] anIntArray = {13, 49, 267, 15, 215};

In the declaration statement above, the variable anIntArray is declared as a reference to an array of ints. The array initializer results in the construction of an array to hold five elements (equal to the length of the list of expressions in the block), where the first element is initialized to the value of the first expression (13), the second element to the value of the second expression (49), and so on.

Click here to view code image

Pizza[] pizzaOrder = { new Pizza(), new Pizza(), null };

In this declaration statement, the variable pizzaOrder is declared as a reference to an array of Pizza objects. The array initializer constructs an array to hold three elements. The initialization code sets the first two elements of the array to refer to two Pizza objects, while the last element is initialized to the null reference. The reference value of the array of Pizza objects is assigned to the reference pizzaOrder. Note also that this declaration statement actually creates three objects: the array object with three references and the two Pizza objects.

The expressions in the array_initialize_list are evaluated from left to right, and the array name obviously cannot occur in any of the expressions in the list. In the preceding examples, the array_initialize_list is terminated by the right curly bracket, }, of the block. The list can also be legally terminated by a comma. The following array has length 2, and not 3:

Click here to view code image

Topping[] pizzaToppings = { new Topping(“cheese”), new Topping(“tomato”), };

The declaration statement at (1) in the following code defines an array of four String objects, while the declaration statement at (2) shows that a String object is not the same as an array of char.

Click here to view code image // Array with 4 String objects:
String[] pets = {“crocodiles”, “elephants”, “crocophants”, “elediles”}; // (1)

// Array of 3 characters:
char[] charArray = {‘a’, ‘h’, ‘a’};    // (2) Not the same as “aha”

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.

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.

The main() Method – Declarations

3.12 The main() Method

The mechanics of compiling and running Java applications using the JDK are outlined in §1.8, p. 19. The java command executes a method called main in the class specified on the command line. This class designates the entry point of the application. Any class can have a main() method, but only the main() method of the class specified in the java command starts the execution of a Java application.

The main() method must have public access so that the JVM can call this method (§6.5, p. 345). It is a static method belonging to the class, so no object of the class is required to start its execution. It does not return a value; that is, it is declared as void. It has an array of String objects as its only formal parameter. This array contains any arguments passed to the program on the command line (see the next subsection). The following method header declarations fit the bill, and any one of them can be used for the main() method:

Click here to view code image

public static void main(String[] args)    // Method header
public static void main(String args[])    // Method header
public static void main(String… args)   // Method header

The three modifiers can occur in any order in the method header. The requirements given in these examples do not exclude specification of other non-access modifiers like final (§5.5, p. 226) or a throws clause (§7.5, p. 388). The main() method can also be overloaded like any other method. The JVM ensures that the main() method having the correct method header is the starting point of program execution.

Program Arguments

Any arguments passed to the program on the command line can be accessed in the main() method of the class specified on the command line. These arguments are passed to the main() method via its formal parameter args of type String[]. These arguments are called program arguments.

In Example 3.16, the program prints the arguments passed to the main() method from the following command line:

Click here to view code image

>
java Colors red yellow green “blue velvet”

The program prints the total number of arguments given by the field length of the String array args. Each string in args, which corresponds to a program argument, is printed together with its length inside a for loop. From the output, we see that there are four program arguments. On the command line, the arguments can be separated by one or more spaces between them, but these are not part of any argument. The last argument shows that we can quote the argument if spaces are to be included as part of the argument.

When no arguments are specified on the command line, a String array of zero length is created and passed to the main() method. Thus the reference value of the formal parameter in the main() method is never null.

Note that the command name java and the class name Colors are not passed to the main() method of the class Colors, nor are any other options that are specified on the command line.

As program arguments can only be passed as strings, they must be explicitly converted to other values by the program, if necessary.

Program arguments supply information to the application, which can be used to tailor the runtime behavior of the application according to user requirements.

Example 3.16 Passing Program Arguments

Click here to view code image

public class Colors {
   synchronized public static void main(String[] args) {
    System.out.println(“No. of program arguments: ” + args.length);
    for (int i = 0; i < args.length; i++)
      System.out.println(“Argument no. ” + i + ” (” + args[i] + “) has ” +
                          args[i].length() + ” characters.”);
  }
}

Running the program:

Click here to view code image >
java Colors red yellow green “blue velvet”

No. of program arguments: 4
Argument no. 0 (red) has 3 characters.
Argument no. 1 (yellow) has 6 characters.
Argument no. 2 (green) has 5 characters.
Argument no. 3 (blue velvet) has 11 characters.

Variable Arity and Fixed Arity Method Calls – Declarations

Variable Arity and Fixed Arity Method Calls

The calls at (1) to (4) in Example 3.15 are all variable arity calls, as an implicit Object array is created in which the values of the actual parameters are stored. The reference value of this array is passed to the method. The printout shows that the type of the parameter is actually an array of Objects ([Ljava.lang.Object;).

The call at (6) differs from the previous calls in that the actual parameter is an array that has the same type (Object[]) as the variable arity parameter, without having to create an implicit array. In such a case, no implicit array is created, and the reference value of the array dateInfo is passed to the method. See also the result from this call at (6) in the output. The call at (6) is a fixed arity call (also called a non-varargs call), where no implicit array is created:

Click here to view code image

flexiPrint(dateInfo);              // (6) Non-varargs call

However, if the actual parameter is cast to the type Object as at (7), a variable arity call is executed:

Click here to view code image

flexiPrint((Object) dateInfo);     // (7) new Object[] {(Object) dateInfo}

The type of the actual argument (Object) is now not the same as that of the variable arity parameter (Object[]), resulting in an array of the type Object[] being created in which the array dateInfo is stored as an element. The printout at (7) shows that only the text representation of the dateInfo array is printed, and not its elements, as it is the sole element of the implicit array.

The call at (8) is a fixed arity call, for the same reason as the call at (6). Now, however, the array dateInfo is explicitly stored as an element in an array of the type Object[] that matches the type of the variable arity parameter:

Click here to view code image

flexiPrint(new Object[]{dateInfo});// (8) Non-varargs call

The output from (8) is the same as the output from (7), where the array dateInfo was passed as an element in an implicitly created array of type Object[].

The compiler issues a warning for the call at (9):

Click here to view code image

flexiPrint(args);                  // (9) Warning!

The actual parameter args is an array of the type String[], which is a subtype of Object[]—the type of the variable arity parameter. The array args can be passed in a fixed arity call as an array of the type String[], or in a variable arity call as an element in an implicitly created array of the type Object[]. Both calls are feasible and valid in this case. Note that the compiler chooses a fixed arity call rather than a variable arity call, but also issues a warning. The result at (9) confirms this course of action. A warning at compile time is not the same as a compile-time error. The former does not prevent the program from being run, whereas the latter does.

At (10), the array args of the type String[] is explicitly passed as an Object in a variable arity call, similar to the call at (7):

Click here to view code image

flexiPrint((Object) args);         // (10) Explicit varargs call

At (11), the array args of type String[] is explicitly passed as an array of the type Object[] in a fixed arity call. This call is equivalent to the call at (9), where the widening reference conversion is implicit, but now without a warning at compile time. The two calls print the same information, as is evident from the output at (9) and (11):

Click here to view code image

flexiPrint((Object[]) args);       // (11) Explicit non-varargs call

The if-else Statement 2 – Control Flow

Example 4.1 Fall-Through in a switch Statement with the Colon Notation

Click here to view code image

public class Advice {
  private static final int LITTLE_ADVICE = 0;
  private static final int MORE_ADVICE = 1;
  private static final int LOTS_OF_ADVICE = 2;
  public static void main(String[] args) {
    dispenseAdvice(LOTS_OF_ADVICE);
  }
  public static void dispenseAdvice(int howMuchAdvice) {
    switch (howMuchAdvice) {                                     // (1)
      case LOTS_OF_ADVICE: System.out.println(“See no evil.”);   // (2)
      case MORE_ADVICE:    System.out.println(“Speak no evil.”); // (3)
      case LITTLE_ADVICE:  System.out.println(“Hear no evil.”);  // (4)
                           break;                                // (5)
      default:             System.out.println(“No advice.”);     // (6)
    }
  }
}

Output from the program:

See no evil.
Speak no evil.
Hear no evil.

Several case labels can prefix the same group of statements. This is the equivalent of specifying the same case constants in a single case label. The latter syntax is preferable as it is more concise than the former. Such case constants will result in the associated group of statements being executed. This behavior is illustrated in Example 4.2 for the switch statement at (1).

At (2) in Example 4.2, three case labels are defined that are associated with the same action. At (3), (4), and (5), a list of case constants is defined for some of the case labels. Note also the use of the break statement to stop fall-through in the switch block after the statements associated with a case label are executed.

The first statement in the switch block must always have a case or default label; otherwise, it will be unreachable. This statement will never be executed because control can never be transferred to it. The compiler will flag this case (no pun intended) as an error. An empty switch block is perfectly legal, but not of much use.

Since each group of statements associated with a case label can be any arbitrary statement, it can also be another switch statement. In other words, switch statements can be nested. Since a switch statement defines its own local block, the case labels in an inner block do not conflict with any case labels in an outer block. Labels can be redefined in nested blocks; in contrast, variables cannot be redeclared in nested blocks (§6.6, p. 354). In Example 4.2, an inner switch statement is defined at (6), which allows further refinement of the action to take on the value of the selector expression in cases where multiple case labels are used in the outer switch statement. A break statement terminates the innermost switch statement in which it is executed.

The print statement at (7) is always executed for the case constants 9, 10, and 11.

Note that the break statement is the last statement in the group of statements associated with each case label. It is easy to think that the break statement is a part of the switch statement syntax, but technically it is not.

Example 4.2 Nested switch Statements with the Colon Notation

Click here to view code image

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

Output from the program:

Click here to view code image

Thanksgiving.
Yellow leaves in the fall.

The if-else Statement – Control Flow

4.2 The switch Statement

The switch construct implements a multi-way branch that allows program control to be transferred to a specific entry point in the code of the switch block based on a computed value. Java has two variants of the switch construct (the switch statement and the switch expression), and each of them can be written in two different ways (one using the colon notation and the other using the arrow notation). This section covers the two forms of the switch statement. Particular details of the switch expression are covered in the next section (p. 164).

The switch Statement with the Colon (:) Notation

We will first look at the switch statement defined using the colon notation, illustrated in Figure 4.2.

Figure 4.2 Form of the switch Statement with the Colon Notation

switch (
selector_expression
) {
 // Switch block with statement groups defined using colon notation:
 case
CC
:                          
statements
 case
CC
1
: case
CC
2
: … case
CC
n

statements

 case
CC
3
,
CC
4
, …,
CC
m
:          
statements

 …
 default: …
}

Conceptually, the switch statement can be used to choose one among many alternative actions, based on the value of an expression. The syntax of the switch statement comprises a selector expression followed by a switch block. The selector expression must evaluate to a value whose type must be one of the following:

  • A primitive data type: char, byte, short, or int
  • A wrapper type: Character, Byte, Short, or Integer
  • An enum type (§5.13, p. 287)
  • The type String (§8.4, p. 439)

Note that the type of the selector expression cannot be boolean, long, or floating-point. The statements in the switch block can have case labels, where each case label specifies one or more case constants (CC), thereby defining entry points in the switch block where control can be transferred depending on the value of the selector expression. The switch block must be compatible with the type of the selector expression, otherwise a compile-time error occurs.

The execution of the switch statement proceeds as follows:

  • The selector expression is evaluated first. If the value is a wrapper type, an unboxing conversion is performed (§2.3, p. 45). If the selector expression evaluates to null, a NullPointerException is thrown.
  • The value of the selector expression is compared with the constants in the case labels. Control is transferred to the start of the statements associated with the case label that has a case constant whose value is equal to the value of the selector expression. Note that a colon (:) prefixes the associated statements that can be any group of statements, including a statement block. After execution of the associated statements, control falls through to the next group of statements, unless this was the last group of statements declared or control was transferred out of the switch statement.
  • If no case label has a case constant that is equal to the value of the selector expression, the statements associated with the default label are executed. After execution of the associated statements, control falls through to the next group of statements, unless this was the last group of statements declared or control was transferred out of the switch statement.

Figure 4.3 illustrates the flow of control through a switch statement where the default label is declared last and control is not transferred out of the switch statement in the preceding group of statements.

Figure 4.3 Activity Diagram for the switch Statement with the Colon Notation

All case labels (including the default label) are optional and can be defined in any order in the switch block. All case labels and the default label are separated from their associated group of statements by a colon (:). A list of case labels can be associated with the same statements, and a case label can specify a comma-separated list of case constants. At most, one default label can be present in a switch statement. If no valid case labels are found and the default label is omitted, the whole switch statement is skipped.

The case constants (CC) in the case labels are constant expressions whose values must be unique, meaning no duplicate values are allowed. In fact, a case constant must be a compile-time constant expression whose value is assignable to the type of the selector expression (§2.4, p. 46). In particular, all case constant values must be in the range of the type of the selector expression. The type of a case constant cannot be boolean, long, or floating-point.

The compiler is able to generate efficient code for a switch statement, as this statement only tests for equality between the selector expression and the constant expressions of the case labels, so as to determine which code to execute at runtime. In contrast, a sequence of if statements determines the flow of control at runtime, based on arbitrary conditions which might be determinable only at runtime.

In Example 4.1, depending on the value of the howMuchAdvice parameter, different advice is printed in the switch statement at (1) in the method dispenseAdvice(). The example shows the output when the value of the howMuchAdvice parameter is LOTS_OF_ADVICE. In the switch statement, the associated statement at (2) is executed, giving one piece of advice. Control then falls through to the statement at (3), giving the second piece of advice. Control next falls through to (4), dispensing the third piece of advice, and finally execution of the break statement at (5) causes control to exit the switch statement. Without the break statement at (5), control would continue to fall through the remaining statements—in this case, to the statement at (6) being executed. Execution of the break statement in a switch block transfers control out of the switch statement (p. 180). If the parameter howMuchAdvice has the value MORE_ADVICE, then the advice at both (3) and (4) is given. The value LITTLE_ADVICE results in only one piece of advice at (4) being given. Any other value results in the default action, which announces that there is no advice.

The associated statement of a case label can be a group of statements (which need not be a statement block). The case label is prefixed to the first statement in each case. This is illustrated by the associated statements for the case constant LITTLE_ADVICE in Example 4.1, which comprises statements (4) and (5).