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.

Local Variable Type Inference – Declarations

3.13 Local Variable Type Inference

A variable declaration requires the type of the variable to be specified in the declaration. However, in the case of local variables, the type can be specified by the reserved type name var, if the local declaration also specifies an initialization expression in the declaration. The compiler uses the type of the initialization expression to infer the type of the local variable. The restricted type name var denotes this inferred type in the local declaration. This is an example of type inference, where the type of a variable or an expression is derived from the context in which it is used. If the compiler cannot infer the type, it reports a compile-time error. A local variable declaration that uses var is also called a var declaration. A local variable declared this way is no different from any other local variable.

It is important to note that the type of the local variable is solely inferred from the initialization expression specified in the declaration. The following variable declaration in a local context (e.g., body of a method) is declared using the reserved type name var:

var year = 2022;

The compiler is able to infer that the type of the initialization expression 2022 is int in the above declaration, and therefore the variable year has the type int. The declaration above is equivalent to the declaration below, where the type is explicitly specified:

int year = 2022;

A cautionary note going forward: This subsection refers to many concepts and constructs that might not be familiar at this stage. It might be a good idea to get an overview now and to come back later for a more thorough review of this topic. The exhaustive index at the end of the book can of course be used at any time to look up a topic.

The class ValidLVTI in Example 3.17 illustrates valid uses of the restricted type name var. The comments in the code should be self-explanatory.

The var restricted type name is allowed in local variable declarations in blocks (including initializer blocks), constructors, and methods, as can be seen in the class ValidLVTI at (1a), (1b), and (2) and the method main(), respectively.

Note that at (3b) and (3c), the compiler is able to infer the type of the local variable from the return type of the method on the right-hand side.

It is worth noting that the cast operator, (), can be necessary to indicate the desired type, as shown at (5) and (7).

For array variables, the initialization expression must be an array creation expression that allows the array size and the array element type to be inferred, as shown at (11a), (11b), (11c), and (11d). A local declaration with var requires an initialization expression, which in the case of local arrays must be either an array creation expression or an anonymous array expression. In other words, it should be possible to infer both the array element type and the size of the array. It cannot be an array initializer.

The bodies (and the headers) of the for(;;) and for(:) loops can define their own local variables in their block scope. The type of the local variable vowel at (13) is inferred to be char from the array vowels (of type char[]) in the header of the for(:) loop. The type of the local variable i in the header of the for(;;) loop at (16) is determined to be int from the initial value. The switch statement also defines its own block scope in which local variables can be declared, as shown at (18).

Example 3.17 Illustrating Local Variable Type Reference

Click here to view code image

// Class ValidLVTI illustrates valid use of the restricted type name var.
public class ValidLVTI {
  // Static initializer block:
  static {
    var slogan = “Keep calm and code Java.”;        // (1a) Allowed in static
  }                                                 //      initializer block
  // Instance initializer block:
  {
    var banner = “Keep calm and catch exceptions.”; // (1b) Allowed in instance
  }                                                 //      initializer block
  // Constructor:
  public ValidLVTI() {
    var luckyNumber = 13;                       // (2) Allowed in a constructor.
  }
  // Method:
  public static void main(String[] args) {

    var virus = “COVID-19”;                     // (3a) Type of virus is String.
    var acronym = virus.substring(0, 5);        // (3b) Type of acronym is String.
    var num = Integer.parseInt(virus.substring(6)); // (3c) Type of num is int.
    var obj = new Object();                     // (4) Type of obj is Object.
    var title = (String) null; // (5) Initialization expression type is String.
                               //     Type of title is String.
    var sqrtOfNumber = Math.sqrt(100); // (6) Type of sqrtOfNumber is double,
                                       //     since the method returns
                                       //     a double value.
    var tvSize  = (short) 55;  // (7) Type of tvSize is short.
    var tvSize2 = 65;          // (8) Type of tvSize2 is int.
    var diameter = 10.0;       // (9) Type of diameter is double.
    var radius = 2.5F;         // (10) Type of radius is float.
    // Arrays:
    var vowels = new char[] {‘a’, ‘e’, ‘i’, ‘o’, ‘u’ }; // (11a) Type of vowels
                                                        // is char[]. Size is 5.
    var zodiacSigns = new String[12]; // (11b) Type of zodiacSigns is String[].
                                      //       Size is 12.
    var a_2x3 = new int[2][3]; // (11c) Type of a_2x3 is int[][]. Size is 2×3.
    var a_2xn = new int[2][];  // (11d) Type of a_2xn is int[][]. Size is 2x?,
                               //       where second dimension can be undefined.
    // The for(:) loop:
    var word1 = “”;            // (12) Type of word2 is String.
    for (var vowel : vowels) { // (13) Type of vowel is char in the for(:)loop.
      var letter = vowel;      // (14) Type of letter is char.
      word1 += letter;
    }
    // The for(;;) loop:
    var word2 = “”;                           // (15) Type of word2 is String.
    for (var i = 0; i < vowels.length; i++) { // (16) Type of i is int in
                                              //      the for loop.
      var letter = vowels[i];                 // (17) Type of letter is char.
      word2 += letter;
    }
    // switch-statement:
    switch(virus) {
      case “Covid-19”:
        var flag = “Needs to be tested.”;     // (18) Type is String.
        // Do testing.
        break;
      default: // Do nothing.
    }
  }
}

Local Variable Type Inference 2 – Declarations

Click here to view code image

// Class InvalidLVTI illustrates invalid use of the restricted type name var.
public class InvalidLVTI {
  var javaVendor = “Oracle”; // (19) Not allowed in instance variable declaration.
  static var javaVersion = 11; // (20) Not allowed in static variable declaration.
  public static void main(var args) { // (21) Not allowed for method parameters.
    var name;              // (22) Not allowed without initialization expression.
    var objRef = null;     // (23) Literal null not allowed.
    var x = 10.0, y = 20.0, z = 40;   // (24) Not allowed in compound declaration.
    var vowelsOnly = {‘a’, ‘e’, ‘i’, ‘o’, ‘u’ }; // (25) Array initializer not
                                                 //      allowed.
    var attendance = new int[];         // (26) Non-empty dimension required.
    var array3Dim = new String[][2][];  // (27) Cannot specify an empty dimension
                                        //      before a non-empty dimension.
    var letters[] = new char[]{‘a’, ‘e’, ‘i’, ‘o’, ‘u’ }; // (28) var not allowed
                                                          //      as element type.
    var prompt = prompt + 1;            // (29) Self-reference not allowed in
                                        //      initialization expression.
  }
  public static var getPlatformName() { // (30) Not allowed as return type.
    return “JDK”;
  }
}

The following examples of invalid uses of the restricted type name var are shown in the class InvalidLVTI in Example 3.17:

  • Not allowed in field variable declarations

The var restricted type name is not allowed in field variable declarations, as shown at (19) and (20).

  • Not allowed in declaring formal parameters

Formal parameters in methods and constructors cannot be declared with var, as shown at (21) for the parameter args in the main() method.

  • Initialization expression is mandatory

The var restricted type name is not allowed in a local variable declaration if an initialization expression is not specified, as shown at (22).

  • Initialization expression cannot be the null literal value

Since the literal null can be assigned to any reference type, a specific type for objRef at (23) cannot be determined. At (5), the cast (String) specifies the type of the initialization expression.

  • Cannot use var in compound declarations

The reserved type name var cannot be used in a compound declaration—that is, a declaration that declares several variables, as shown at (24).

  • Cannot use var when an array initializer is specified

As shown at (25), an array initializer cannot be used in a var declaration. However, an array initialization expression is allowed, as at (11a).

  • Array creation expression must specify the size

As in the case when an explicit type is specified for an array variable, the array creation expressions in the declaration must also specify the array size when using var; otherwise, the compiler will issue an error, as at (26) and (27). Valid array creation expressions specifying correct size are shown at (11b), (11c), and (11d).

  • Cannot use var as an array element type

The square brackets ([]) on the left-hand side at (28) are not allowed, as they indicate that the local variable is an array. Array type and size are solely determined from the initialization expression, as at (11a), (11b), (11c), and (11d).

  • Cannot have a self-reference in an initialization expression

As in the case when an explicit type is specified for the local variable, the initialization expression cannot refer to the local variable being declared, as at (29), where the variable is not initialized before use.

  • Cannot use var as the return type of a method

The method declaration at (30) cannot specify the return type using var.

  • A type cannot be a named var

As var is a reserved type name, it is not a valid name for a reference type; that is, a class, an interface, or an enum cannot be named var. In other contexts, it can be used as an identifier, but this is not recommended.

Click here to view code image

public class var {}   // var is not permitted as a class name. Compile-time error!

The reserved type name var should be used judiciously as the code can become difficult to understand. When reading the local declaration below, the initialization expression does not divulge any information about the type, and the names are not too helpful:

var x = gizmo.get();

Unless it is intuitively obvious, a human reader will have to resort to the API documentation in order to infer the type. Using intuitive names becomes even more important when using the reserved type name var.

We will revisit the restricted type name var when discussing exception handling with try-with-resources (§7.7, p. 407), using generics in local variable declarations (§11.2, p. 571), and specifying inferred-type lambda parameters (§13.2, p. 680).

Selection Statements – Control Flow

4.1 Selection Statements

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:

Click here to view code image

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.

Click here to view code image

if (emergency); // Empty if block
  operate();    // Executed regardless of whether it was an emergency

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 – Control Flow

The if-else Statement

The if-else statement is used to decide between two actions, based on a condition. It has the following syntax:

if (
condition
)
statement
1
else
statement
2

The condition is evaluated first. If its value is true (or unboxed to true), statement1 (the if block) is executed and then execution continues with the rest of the program. If the value is false (or unboxed to false), statement2 (the else block) is executed and then execution continues with the rest of the program. In other words, one of two mutually exclusive actions is performed. The else clause is optional; if omitted, the construct is equivalent to the simple if statement. The semantics are illustrated by the activity diagram in Figure 4.1b.

In the following examples of the if-else statement, it is assumed that all variables and methods have been appropriately defined:

Click here to view code image

if (emergency)
  operate();
else
  joinQueue();
if (temperature > critical)
  soundAlarm();
else
  businessAsUsual();
if (catIsAway()) {
  getFishingRod();
  goFishing();
} else
  playWithCat();

Since actions can be arbitrary statements, the if statements can be nested.

Click here to view code image

if (temperature >= upperLimit) {        // (1)
  if (danger)                           // (2) Simple if.
    soundAlarm();
  if (critical)                         // (3)
    evacuate();
  else                                  // Goes with if at (3).
    turnHeaterOff();
} else                                  // Goes with if at (1).
    turnHeaterOn();

The use of block notation, {}, can be critical to the execution of if statements. The if statements (A) and (B) in the following examples do not have the same meaning. The if statements (B) and (C) are the same, with extra indentation used in (C) to make the meaning evident. Leaving out the block notation in this case could have catastrophic consequences: The heater could be turned on when the temperature is above the upper limit.

Click here to view code image

// (A):
if (temperature > upperLimit) {          // (1) Block notation.
  if (danger) soundAlarm();              // (2)
} else                                   // Goes with if at (1).
  turnHeaterOn();
// (B):
if (temperature > upperLimit)            // (1) Without block notation.
  if (danger) soundAlarm();              // (2)
else turnHeaterOn();                     // Goes with if at (2).
// (C):
if (temperature > upperLimit)            // (1)
  if (danger)                            // (2)
    soundAlarm();
  else                                   // Goes with if at (2).
    turnHeaterOn();

The rule for matching an else clause is that an else clause always refers to the nearest if that is not already associated with another else clause. Block notation and proper indentation can be used to make the meaning obvious.

Cascading of if-else statements comprises a sequence of nested if-else statements where the if block of the next if-else statement is joined to the else clause of the previous if-else statement. The decision to execute a block is then based on all the conditions evaluated so far.

Click here to view code image

if (temperature >= upperLimit) {                           // (1)
  soundAlarm();
  turnHeaterOff();
} else if (temperature < lowerLimit) {                     // (2)
  soundAlarm();
  turnHeaterOn();
} else if (temperature == (upperLimit-lowerLimit)/2) {     // (3)
  doingFine();
} else                                                     // (4)
  noCauseToWorry();

The block corresponding to the first if condition that evaluates to true is executed, and the remaining if statements are skipped. In the preceding example, the block at (3) will execute only if the conditions at (1) and (2) are false and the condition at (3) is true. If none of the conditions is true, the block associated with the last else clause is executed. If there is no last else clause, no actions are performed.

Calling a Variable Arity Method – Declarations

Calling a Variable Arity Method

Example 3.15 illustrates various aspects of calling a variable arity method. The method flexiPrint() in the VarargsDemo class has a variable arity parameter:

Click here to view code image

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.

Example 3.15 Calling a Variable Arity Method

Click here to view code image

public class VarargsDemo {
  public static void flexiPrint(Object… data) { // Object[]
    // Print the name of the Class object for the varargs parameter.
    System.out.print(“Type: ” + data.getClass().getName());
    System.out.println(”  No. of elements: ” + data.length);
    System.out.print(“Element values: “);
    for(Object element : data)
      System.out.print(element + ” “);
    System.out.println();
  }
  public static void main(String… args) {
    int    day       = 13;
    String monthName = “August”;
    int    year      = 2009;
    // Passing primitives and non-array types:
    flexiPrint();                      // (1) new Object[] {}
    flexiPrint(day);                   // (2) new Object[] {Integer.valueOf(day)}
    flexiPrint(day, monthName);        // (3) new Object[] {Integer.valueOf(day),
                                       //                   monthName}
    flexiPrint(day, monthName, year);  // (4) new Object[] {Integer.valueOf(day),
                                       //                   monthName,
                                       //                   Integer.valueOf(year)}
    System.out.println();
    // Passing an array type:
    Object[] dateInfo = {day,          // (5) new Object[] {Integer.valueOf(day),
                         monthName,    //                   monthName,
                         year};        //                   Integer.valueOf(year)}
    flexiPrint(dateInfo);              // (6) Non-varargs call
    flexiPrint((Object) dateInfo);     // (7) new Object[] {(Object) dateInfo}
    flexiPrint(new Object[]{dateInfo});// (8) Non-varargs call
    System.out.println();
    // Explicit varargs or non-varargs call:
    flexiPrint(args);                  // (9) Warning!
    flexiPrint((Object) args);         // (10) Explicit varargs call
    flexiPrint((Object[]) args);       // (11) Explicit non-varargs call
  }
}

Compiling the program:

Click here to view code image

>
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

Running the program:

Click here to view code image

>
java VarargsDemo To arg or not to arg

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

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.

Passing Reference Values – Declarations

Passing Reference Values

If the actual parameter expression evaluates to a reference value, the resulting reference value on the stack is assigned to the corresponding formal parameter reference at method invocation. In particular, if an actual parameter is a reference to an object, the reference value stored in the actual parameter is passed. Consequently, both the actual parameter and the formal parameter are aliases to the object denoted by this reference value during the invocation of the method. In particular, this implies that changes made to the object via the formal parameter will be apparent after the call returns.

Type conversions between actual and formal parameters of reference types are discussed in §5.10, p. 265.

In Example 3.11, a Pizza object is created at (1). Any object of the class Pizza created using the class declaration at (5) always results in a beef pizza. In the call to the bake() method at (2), the reference value of the object referenced by the actual parameter favoritePizza is assigned to the formal parameter pizzaToBeBaked in the declaration of the bake() method at (3).

Example 3.11 Passing Reference Values

Click here to view code image

public class CustomerTwo {
  public static void main (String[] args) {
    Pizza favoritePizza = new Pizza();              // (1)
    System.out.println(“Meat on pizza before baking: ” + favoritePizza.meat);
    bake(favoritePizza);                            // (2)
    System.out.println(“Meat on pizza after baking: ” + favoritePizza.meat);
  }
  public static void bake(Pizza pizzaToBeBaked) {   // (3)
    pizzaToBeBaked.meat = “chicken”;  // Change the meat on the pizza.
    pizzaToBeBaked = null;                          // (4)
  }
}

class Pizza {                                       // (5)
  String meat = “beef”;
}

Output from the program:

Click here to view code image

Meat on pizza before baking: beef
Meat on pizza after baking: chicken

One particular consequence of passing reference values to formal parameters is that any changes made to the object via formal parameters will be reflected back in the calling method when the call returns. In this case, the reference favoritePizza will show that chicken has been substituted for beef on the pizza. Setting the formal parameter pizzaToBeBaked to null at (4) does not change the reference value in the actual parameter favoritePizza. The situation at method invocation, and just before the return from method bake(), is illustrated in Figure 3.3.

Figure 3.3 Parameter Passing: Reference Values

In summary, the formal parameter can only change the state of the object whose reference value was passed to the method.

The parameter passing strategy in Java is call by value and not call by reference, regardless of the type of the parameter. Call by reference would have allowed values in the actual parameters to be changed via formal parameters; that is, the value in pricePrPizza would be halved in Example 3.10 and favoritePizza would be set to null in Example 3.11. However, this cannot be directly implemented in Java.

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).