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:

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

The switch Expression with the Arrow (->) Notation

The switch expression with the arrow notation also has the same form as the switch statement with the arrow notation (Figure 4.4), except that the execution of the switch body must result in a value (or it must throw an exception).

The execution of the switch rules in a switch expression is mutually exclusive, analogous to the switch rules in a switch statement (Figure 4.5). Once the action in the switch rule has completed execution, the value computed by the action is returned and the execution of the switch expression terminates. There is no fall-through and no break statement is allowed.

Whereas the actions in the switch rules of a switch statement only allowed an expression statement (Figure 4.5), the actions in the switch rules of a switch expression allow any expression, in addition to allowing a block or throwing an exception, as in the switch rules of a switch statement.

By far, the canonical action of a case label in a switch rule of a switch expression is an arbitrary expression. Such an expression is always terminated by a semicolon (;). The expression value is returned as the value of the switch expression whose execution is then terminated. Note that no yield statement is necessary or allowed.

Click here to view code image


case 1 -> “ONE”;
case 2 -> yield “two”;       // Compile-time error!

A block of statements can be used if program logic should be refined, but the last statement in the block should be a yield statement to return its value and terminate the execution of the switch expression (alternatively, the last statement can be a throw statement). In a switch expression with the arrow notation, the yield statement is only allowed as the last statement in a block that constitutes the action in a switch rule.

Click here to view code image


case ALARM     -> { soundTheAlarm();
                    callTheFireDepartment();
                    yield Status.EVACUATE; }  // OK
case ALL_CLEAR -> { yield Status.NORMAL; // Compile-time error: not last statement
                    standDown(); }       //                     in the block.

As the switch rules must be exhaustive, one way to achieve exhaustiveness is to throw an exception as the action in the default label.

Click here to view code image


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

Example 4.6 has been refactored to use the switch expression with the arrow notation in Example 4.7. Each action associated with a case label of a switch rule is a block of statements, where a yield statement is the last statement in a block. If this is not the case, the code will not compile.

The switch expression with the arrow notation must also be exhaustive. Again a non-exhaustive switch expression with the arrow notation will result in a compile-time error. In Example 4.7, the type of the selector expression is int, but the switch rules only cover the int values from 1 to 12. A default label is necessary to make the switch expression exhaustive, as shown at (11).

Example 4.7 Statement Blocks in a switch Expression with the Arrow Notation

Click here to view code image

public class SeasonsIV {
  enum Season { WINTER, SPRING, SUMMER, FALL }                // (1)

  public static void main(String[] args) {
    int monthNumber = 11;
    Season season = switch(monthNumber) {                     // (2)
      case 12, 1, 2 -> {                                      // (3)
        System.out.println(“Snow in the winter.”);
        yield Season.WINTER;                                  // (4)
      }
      case 3, 4, 5 -> {                                       // (5)
        System.out.println(“Green grass in the spring.”);
        yield Season.SPRING;                                  // (6)
      }
      case 6, 7, 8 -> {                                       // (7)
        System.out.println(“Sunshine in the summer.”);
        yield Season.SUMMER;                                  // (8)
      }
      case 9, 10, 11 -> {                                     // (9)
        System.out.println(“Yellow leaves in the fall.”);
        yield Season.FALL;                                    // (10)
      }
      default ->                                              // (11)
        throw new IllegalArgumentException(monthNumber + ” not a valid month.”);
    };                                                        // (12)
    System.out.println(season);
  }
}

Output from the program:

Click here to view code image

Yellow leaves in the fall.
FALL

Example 4.8 is a reworking of Example 4.7 that defines expressions as the actions in the switch rules. No yield statement is necessary or allowed in this case. The switch expression is also exhaustive.

Example 4.8 Expression Actions in a switch Expression with the Arrow Notation

Click here to view code image

public class SeasonsV {
  enum Season { WINTER, SPRING, SUMMER, FALL }                    // (1)
  public static void main(String[] args) {
    int monthNumber = 11;
    Season season = switch(monthNumber) {                         // (2)
      case 12,  1,  2 -> Season.WINTER;                           // (3)
      case  3,  4,  5 -> Season.SPRING;                           // (4)
      case  6,  7,  8 -> Season.SUMMER;                           // (5)
      case  9, 10, 11 -> Season.FALL;                             // (6)
      default         -> throw new IllegalArgumentException(monthNumber +
                                                           ” not a valid month.”);
    };
    System.out.println(season);
  }
}

Output from the program:

FALL

The switch expression can only evaluate to a single value. Multiple values can be returned by constructing an object with the required values and returning the object as a result of evaluating the switch expression. Record classes are particularly suited for this purpose (§5.14, p. 299). The switch expression at (3) in Example 4.9 returns an object of the record class SeasonInfo, defined at (2), to return the month number and the season in which it occurs.

Example 4.9 Returning Multiple Values as a Record from a switch Expression

Click here to view code image

public class SeasonsVI {
  enum Season { WINTER, SPRING, SUMMER, FALL }                              // (1)
  record SeasonInfo(int month, Season season) {}                            // (2)
  public static void main(String[] args) {
    int monthNumber = 11;
    SeasonInfo seasonInfo = switch(monthNumber) {                           // (3)
      case 12,  1,  2 -> new SeasonInfo(monthNumber, Season.WINTER);        // (4)
      case  3,  4,  5 -> new SeasonInfo(monthNumber, Season.SPRING);        // (5)
      case  6,  7,  8 -> new SeasonInfo(monthNumber, Season.SUMMER);        // (6)
      case  9, 10, 11 -> new SeasonInfo(monthNumber, Season.FALL);          // (7)
      default         -> throw new IllegalArgumentException(monthNumber +
                                                           ” not a valid month.”);
    };
    System.out.println(seasonInfo);
  }
}

Output from the program:

Click here to view code image SeasonInfo[month=11, season=FALL]

Parameter Passing – Declarations

3.10 Parameter Passing

Objects communicate by calling methods on each other. A method call is used to invoke a method on an object. Parameters in the method call provide one way of exchanging information between the caller object and the callee object (which need not be different).

The syntax of a method call can be any one of the following:

Click here to view code image

object_reference.
method_name
(
actual_parameter_list
)
class_name.
static_method_name
(
actual_parameter_list
)
method_name
(
actual_parameter_list
)

The object_reference must be an expression that evaluates to a reference value denoting the object on which the method is called. If the caller and the callee are the same, object reference can be omitted (see the discussion of the this reference on p. 106). The class_name can be the fully qualified name (§6.3, p. 326) of the class. The actual_parameter_list is comma-separated if there is more than one parameter. The parentheses are mandatory even if the actual parameter list is empty. This distinguishes the method call from field access. One can specify fully qualified names for classes and packages using the dot operator (.).

Click here to view code image

objRef.doIt(time, place);         // Explicit object reference
int i = java.lang.Math.abs(-1);   // Fully qualified class name
int j = Math.abs(-1);             // Simple class name
someMethod(ofValue);              // Object or class is implied
someObjRef.make().make().make();  // make() returns a reference value

The dot operator (.) has left associativity. In the last line of the preceding code, the first call of the make() method returns a reference value that denotes the object on which to execute the next call, and so on. This is an example of call chaining.

Each actual parameter (also called an argument) is an expression that is evaluated, and whose value is passed to the method when the method is invoked. Its value can vary from invocation to invocation. Formal parameters are parameters defined in the method declaration and are local to the method.

It should also be stressed that each invocation of a method has its own copies of the formal parameters, as is the case for any local variables in the method. The JVM uses a stack to keep track of method execution and a heap to manage the objects that are created by the program (§7.1, p. 365). Values of local variables and those passed to the method as parameters, together with any temporary values computed during the execution of the method, are always stored on the stack. Thus only primitive values and reference values are stored on the stack, and only these can be passed as parameters in a method call, but never any object from the heap.

In Java, all parameters are passed by value—that is, an actual parameter is evaluated and its value from the stack is assigned to the corresponding formal parameter. Table 3.2 summarizes the value that is passed depending on the type of the parameters. In the case of primitive data types, the data value of the actual parameter is passed. If the actual parameter is a reference to an object, the reference value of the denoted object is passed and not the object itself. Analogously, if the actual parameter is an array element of a primitive data type, its data value is passed, and if the array element is a reference to an object, then its reference value is passed.

Table 3.2 Parameter Passing by Value

Data type of the formal parameterValue passed
Primitive data typePrimitive data value of the actual parameter
Reference type (i.e., class, interface, array, or enum type)Reference value of the actual parameter

The order of evaluation in the actual parameter list is always from left to right. The evaluation of an actual parameter can be influenced by an earlier evaluation of an actual parameter. Given the following declaration:

int i = 4;

the method call

leftRight(i++, i);

is effectively the same as

leftRight(4, 5);

and not the same as

leftRight(4, 4);

An overview of the conversions that can take place in a method invocation context is provided in §2.4, p. 48. Method invocation conversions for primitive values are discussed in the next subsection (p. 129), and those for reference types are discussed in §5.10, p. 265. Calling variable arity methods is discussed in the next section (p. 136).

For the sake of simplicity, the examples in subsequent sections primarily show method invocation on the same object or the same class. The parameter passing mechanism is no different when different objects or classes are involved.

Arrays – Declarations

3.9 Arrays

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

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

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

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

Declaring Array Variables

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

element_type
[]
array_name
;

or

element_type array_name
[];

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

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

Click here to view code image

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

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

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

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

The switch Statement with the Arrow (->) Notation

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

Figure 4.4 Form of the switch Statement with the Arrow Notation

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

  case
CC
4
                 ->
throw_statement

  …
  default                  -> …
}

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

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

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

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

Click here to view code image


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

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

Click here to view code image


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

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

Click here to view code image


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

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

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

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

Example 4.3 Nested switch Statements with the Arrow Notation

Click here to view code image

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

Output from the program:

Click here to view code image

Thanksgiving.
Yellow leaves in the fall.

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.

Variable Arity Methods – Declarations

3.11 Variable Arity Methods

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

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

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

Click here to view code image

type

formal_parameter_name

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

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

Click here to view code image

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

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

type
[]

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

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

Click here to view code image

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

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

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

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

Click here to view code image

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

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

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

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

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.

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.