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:

Using Strings as case Constants – Control Flow

Using Strings as case Constants

Example 4.4 illustrates using strings in a switch statement. The thing to note is what constitutes a constant string expression that can be used as a case constant. The case constants at (3), (4), (5), and (6) are all valid constant string expressions, as the compiler can figure out their value at compile time. String literals, used at (3) and (6), and constant field values, declared at (1) and (2a) and used at (4) and (5), are all valid case constants. In contrast, the HOT reference from declarations (2b) and (2c) cannot be used as a case constant. From the declaration at (2a), the compiler cannot guarantee that the value of the reference will not change at runtime. From the declaration at (2c), it cannot deduce the value at compile time, as the constructor must be run to construct the value.

Switching on strings is essentially based on equality comparison of integer values that are hash values of strings, followed by an object equality test to rule out the possibility of collision between two different strings having the same hash value. Switching on strings should be used judiciously, as it is less efficient than switching on integers. Switching on strings is not advisable if the values being switched on are not already strings.

Example 4.4 Strings in a switch Statement

Click here to view code image

public class SwitchingOnAString {
  public static final String MEDIUM = “Medium”;       // (1)
  public static final String HOT = “Hot”;             // (2a)
//public static       String HOT = “Hot”;             // (2b) Not OK as case label
//public static final String HOT = new String(“Hot”); // (2c) Not OK as case label
  public static void main(String[] args) {
    String spiceLevel = “Medium_Hot”;
    switch (spiceLevel) {
      case “Mild”,                                                        // (3)
            MEDIUM + “_” + HOT -> System.out.println(“Enjoy your meal!”); // (4)
      case HOT                 -> System.out.println(“Have fun!”);        // (5)
      case “Suicide”           -> System.out.println(“Good luck!”);       // (6)
      default                  -> System.out.println(“You being funny?”);
    }
  }
}

Output from the program:

Enjoy your meal!

Using Enum Constants as case Constants

Example 4.5 illustrates the use of enum types (§5.13, p. 287) in a switch statement with the arrow notation. The enum type SpiceGrade is defined at (1). The type of the selector expression at (2) is the enum type SpiceGrade. Note that the enum constants are not specified with their fully qualified name (see (3a)). Using the fully qualified name results in a compile-time error, as shown at (3b). Only enum constants that have the same enum type as the selector expression can be specified as case label values.

The semantics of the switch statement are the same as described earlier. Switching on enum values is essentially based on equality comparison of unique integer values that are ordinal values assigned by the compiler to the constants of an enum type.

When the switch rules cover all values of the selector expression type, the switch statement is said to be exhaustive. Non-exhaustive switch statements are a common cause of programming errors. It is up to the programmer to ensure that the switch statement is exhaustive, as the compiler does not provide any help in this regard for the switch statement. Judicious use of the default label should be considered, as illustrated in the examples provided in this section that use the switch statement.

Example 4.5 Enums in a switch Statement

Click here to view code image

enum SpiceGrade { MILD, MEDIUM, MEDIUM_HOT, HOT, SUICIDE; }   // (1)
public class SwitchingFun {
  public static void main(String[] args) {
    SpiceGrade spicing = SpiceGrade.HOT;
    switch (spicing) {                                // (2)
      case HOT ->  System.out.println(“Have fun!”);   // (3a) OK!
//    case SpiceGrade.HOT                             // (3b) Compile-time error!
//         -> System.out.println(“Have fun!”);
      case SUICIDE -> System.out.println(“Good luck!”);
      default -> System.out.println(“Enjoy your meal!”);
    }
  }
}

Output from the program:

Have fun!

The do-while Statement – Control Flow

4.6 The do-while Statement

The syntax of the do-while loop is

do
loop_body

while (
loop_condition
);

In a do-while statement, the loop condition is evaluated after executing the loop body. The loop condition must evaluate to a boolean or Boolean value. The value of the loop condition is subjected to unboxing if it is of the type Boolean. The do-while statement executes the loop body until the loop condition becomes false. When the loop condition becomes false, the loop is terminated and execution continues with any statement immediately following the loop. Note that the loop body is executed at least once. Figure 4.7 illustrates the flow of control in a do-while statement.

Figure 4.7 Activity Diagram for the do-while Statement

The loop body in a do-while loop is invariably a statement block. It is instructive to compare the while and do-while loops. In the examples that follow, the mice might never get to play if the cat is not away, as in the loop at (1). The mice do get to play at least once (at the peril of losing their life) in the loop at (2).

Click here to view code image

while (cat.isAway()) {       // (1)
  mice.play();
}
do {                         // (2)
  mice.play();
} while (cat.isAway());

4.7 The for(;;) Statement

The for(;;) loop is the most general of all the loops. It is mostly used for counter-controlled loops, in which the number of iterations is known beforehand.

The syntax of the loop is as follows:

Click here to view code image

for (
initialization
;
loop_condition
;
update_expression
)
loop_body

The initialization usually declares and initializes a loop variable that controls the execution of the loop body. The loop body can be a single statement or a statement block. The loop condition must evaluate to a boolean or Boolean value. In the latter case, the reference value is converted to a boolean value by unboxing. The loop condition usually involves the loop variable, and if the loop condition is true, the loop body is executed; otherwise, execution continues with any statement following the for(;;) loop. After each iteration (i.e., execution of the loop body), the update expression is executed. This usually modifies the value of the loop variable to ensure eventual loop termination. The loop condition is then tested to determine whether the loop body should be executed again. Note that the initialization is executed only once, on entry into the loop. The semantics of the for(;;) loop are illustrated in Figure 4.8, and are summarized by the following equivalent while loop code template:

Click here to view code image

initialization
while (
loop_condition
) {
loop_body
update_expression
}

Figure 4.8 Activity Diagram for the for Statement

The following code creates an int array and sums the values in the array:

Click here to view code image

int sum = 0;
int[] array = {12, 23, 5, 7, 19};
for (int index = 0; index < array.length; index++)   // (1)
  sum += array[index];

The loop variable index is declared and initialized in the initialization section of the loop. It is incremented in the update expression section. This loop is an example of a forward for(;;) loop, where the loop variable is incremented.

The next code snippet is an example of a backward for(;;) loop, where the loop variable is decremented to sum the values in the array:

Click here to view code image

int sum = 0;
int[] array = {12, 23, 5, 7, 19};
for (int index = array.length – 1; index >= 0; index–)
  sum += array[index];

It is instructive to compare the specification of the loop header in the forward and backward for(;;) loops in these examples.

The loop at (1) earlier showed how a declaration statement can be specified in the initialization section. Such a declaration statement can also specify a comma-separated list of variables:

Click here to view code image

for (int i = 0, j = 1, k = 2; … ; …) …;      // (2)

The variables i, j, and k in the declaration statement all have type int. All variables declared in the initialization section are local variables in the for(;;) statement and obey the scope rules for local blocks, as do any variables declared in the loop body. The following code will not compile, however, as variable declarations of different types (in this case, int and String) require declaration statements that are terminated by semicolons:

Click here to view code image

for (int i = 0, String str = “@”; … ; …) …;  // (3) Compile-time error

The initialization section can also be a comma-separated list of expression statements (§3.3, p. 101). Any value returned by an expression statement is discarded. For example, the loop at (2) can be rewritten by factoring out the variable declarations:

Click here to view code image

int i, j, k;                                       // Variable declaration
for (i = 0, j = 1, k = 2; … ; …) …;          // (4) Only initialization

The initialization section is now a comma-separated list of three expressions. The expressions in such a list are always evaluated from left to right, and their values are discarded. Note that the variables i, j, and k at (4) are not local to the loop.

Declaration statements cannot be mixed with expression statements in the initialization section, as is the case at (5) in the following example. Factoring out the variable declaration, as at (6), leaves a legal comma-separated list of expression statements.

Click here to view code image

// (5) Not legal and ugly:
for (int i = 0, System.out.println(“This won’t do!”); flag; i++) { // Error!
  // loop body
}
// (6) Legal, but still ugly:
int i;                                       // Declaration factored out.
for (i = 0, System.out.println(“This is legal!”); flag; i++) {     // OK.
  // loop body
}

The update expression can also be a comma-separated list of expression statements. The following code specifies a for(;;) loop that has a comma-separated list of three variables in the initialization section, and a comma-separated list of two expressions in the update expression section:

Click here to view code image

// Legal usage but not recommended, as it can affect code comprehension.
int[][] sqMatrix = { {3, 4, 6}, {5, 7, 4}, {5, 8, 9} };
for (int i = 0, j = sqMatrix[0].length – 1, asymDiagonal = 0;  // initialization
     i < sqMatrix.length;                                      // loop condition
     i++, j–)                                          // update expression
  asymDiagonal += sqMatrix[i][j];                       // loop body

All sections in the for(;;) header are optional. Any or all of them can be left empty, but the two semicolons are mandatory. In particular, leaving out the loop condition signifies that the loop condition is true. The “crab”, (;;), can be used to construct an infinite loop, where termination is presumably achieved through code in the loop body (see the next section on transfer statements):

Click here to view code image for (;;) doProgramming();       // Infinite loop

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.

Anonymous Arrays – Declarations

Anonymous Arrays

As shown earlier in this section, the following declaration statement can be used to construct arrays using an array creation expression:

Click here to view code image

element_type
1
[]
array_name
 = new
element_type
2
[
array_size
];   // (1)

int[] intArray = new int[5];

The size of the array is specified in the array creation expression, which creates the array and initializes the array elements to their default values. By comparison, the following declaration statement both creates the array and initializes the array elements to specific values given in the array initializer:

Click here to view code image

element_type
[]
array_name
 = {
array_initialize_list
 }; // (2)

int[] intArray = {3, 5, 2, 8, 6};

However, the array initializer is not an expression. Java has another array creation expression, called an anonymous array, which allows the concept of the array creation expression from (1) to be combined with the array initializer from (2), so as to create and initialize an array:

Click here to view code image

new
element_type
[] {
array_initialize_list
 }

new int[] {3, 5, 2, 8, 6}

This construct has enough information to create a nameless array of a specific type and specific length. Neither the name of the array nor the size of the array is specified. The construct returns the reference value of the newly created array, which can be assigned to references and passed as arguments in method calls. In particular, the following declaration statements are equivalent:

Click here to view code image

int[] intArray = {3, 5, 2, 8, 6};                               // (1)
int[] intArray = new int[] {3, 5, 2, 8, 6};                     // (2)

At (1), an array initializer is used to create and initialize the elements. At (2), an anonymous array expression is used. It is tempting to use the array initializer as an expression—for example, in an assignment statement, as a shortcut for assigning values to array elements in one go. However, this is not allowed; instead, an anonymous array expression should be used. The concept of the anonymous array combines the definition and the creation of the array into one operation.

Click here to view code image

int[] daysInMonth;
daysInMonth = {31, 28, 31, 30, 31, 30,
               31, 31, 30, 31, 30, 31};                   // Compile-time error
daysInMonth = new int[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // OK

In Example 3.8, an anonymous array is constructed at (1), and passed as an actual parameter to the static method findMinimum() defined at (2). Note that no array name or array size is specified for the anonymous array.

Example 3.8 Using Anonymous Arrays

Click here to view code image

public class AnonArray {
  public static void main(String[] args) {
    System.out.println(“Minimum value: ” +
        findMinimum(new int[] {3, 5, 2, 8, 6}));                   // (1)
  }
  public static int findMinimum(int[] dataSeq) {                   // (2)
    // Assume the array has at least one element.
    int min = dataSeq[0];
    for (int index = 1; index < dataSeq.length; ++index)
      if (dataSeq[index] < min)
        min = dataSeq[index];
    return min;
  }
}

Output from the program: Minimum value: 2

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”

Passing Primitive Data Values – Declarations

Passing Primitive Data Values

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

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

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

These conversions are illustrated by invoking the following method

Click here to view code image

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

with the following code:

Click here to view code image

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

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

Example 3.10 Passing Primitive Values

Click here to view code image

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

Output from the program:

Click here to view code image

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

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

Figure 3.2 Parameter Passing: Primitive Data Values

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

Using an Array – Declarations

Using an Array

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

Click here to view code image

array_name
 [
index_expression
]

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

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

Click here to view code image

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

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

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

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

Example 3.7 Using Arrays

Click here to view code image

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

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

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

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

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

Probable output from the program: 6.9330
2.7819
6.7427
18.0849
26.2462

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