A statement is a single "command" that is executed by the Java interpreter. By default, the Java interpreter runs one statement after another, in the order they are written. Many of the statements defined by Java, however, are flow-control statements, such as conditionals and loops, that alter this default order of execution in well-defined ways. Table 2-6 summarizes the statements defined by Java.
As we saw earlier in the chapter, certain types of Java expressions have side effects. In other words, they do not simply evaluate to some value, but also change the program state in some way. Any expression with side effects can be used as a statement simply by following it with a semicolon. The legal types of expression statements are assignments, increments and decrements, method calls, and object creation. For example:
a = 1; // Assignment x *= 2; // Assignment with operation i++; // Post-increment --c; // Pre-decrement System.out.println("statement"); // Method invocation
A compound statement is any number and kind of statements grouped together within curly braces. You can use a compound statement anywhere a statement is required by Java syntax:
for(int i = 0; i < 10; i++) { a[i]++; // Body of this loop is a compou nd statement. b[i]--; // It consists of two expression statements } // within curly braces.
An empty statement in Java is written as a single semicolon. The empty statement doesn't do anything, but the syntax is occasionally useful. For example, you can use it to indicate an empty loop body of a for loop:
for(int i = 0; i < 10; a[i++]++) // Increment array elements /* empty */; // Loop body is empty statement
A labeled statement is simply a statement that has been given a name by prepending a identifier and a colon to it. Labels are used by the break and continue statements. For example:
rowLoop: for(int r = 0; r < rows.length; r++) { // A labeled loop colLoop: for(int c = 0; c < columns.length; c++) { // Another one break rowLoop; // Use a label } }
A local variable, often simply called a variable, is a symbolic name for a location where a value can be stored that is defined within a method or compound statement. All variables must be declared before they can be used; this is done with a variable declaration statement. Because Java is a strongly typed language, a variable declaration specifies the type of the variable, and only values of that type can be stored in the variable.
In its simplest form, a variabration specifies a variable's type and name:
int counter; String s;
A variable declaration can also include an initializer : an expression that specifies an initial value for the variable. For example:
int i = 0; String s = readLine(); int[] data = {x+1, x+2, x+3}; // Array initializers are documented later
The Java compiler does not allow you to use a variable that has not been initialized, so it is usually convenient to combine variable declaration and initialization into a single statement. The initializer expression need not be a literal value or a constant expression that can be evaluated by the compiler; it can be an arbitrarily complex expression whose value is computed when the program is run.
A single variable declaration statement can declare and initialize more than one variable, but all variables must be of the same type. Variable names and optional initializers are separated from each other with commas:
int i, j, k; float x = 1.0, y = 1.0; String question = "Really Quit?", response;
In Java 1.1 and later, variable declaration statements can begin with the final keyword. This modifier specifies that once an initial value is specified for the variable, that value is never allowed to change:
final String greeting = getLocalLanguageGreeting();
C programmers should note that Java variable declaration statements can appear anywhere in Java code; they are not restricted to the beginning of a method or block of code. Local variable declarations can also be integrated with the initialize portion of a for loop, as we'll discuss shortly.
Local variables can be used only within the method or block of code in which they are defined. This is called their scope or lexical scope :
void method() { // A generic method int i = 0; // Declare variable i while (i < 10) { // i is in scope here int j = 0; // Declare j; i and j are in scope here } // j is no longer in scope; can't use it anymore System.out.println(i); // i is still in scope here } // The scope of i ends here
The if statement is the fundamental control statement that allows Java to make decisions or, more precisely, to execute statements conditionally. The if statement has an associated expression and statement. If the expression evaluates to true, the interpreter executes the statement. If the expression evaluates to false, however, the interpreter skips the statement. For example:
if (username == null) // If username is null, username = "John Doe"; // define it.
Although they look extraneous, the parentheses around the expression are a required part of the syntax for the if statement.
As I already mentioned, a block of statements enclosed in curly braces is itself a statement, so we can also write if statements that look as follows:
if ((address == null) || (address.equals(""))) { address = "[undefined]"; System.out.println("WARNING: no address specified."); }
An if statement can include an optional else keyword that is fby a second statement. In this form of the statement, the expression is evaluated, and, if it is true, the first statement is executed. Otherwise, the second statement is executed. For example:
if (username != null) System.out.println("Hello " + username); else { username = askQuestion("What is your name?"); System.out.println("Hello " + username + ". Welcome!"); }
When you use nested if/else statements, some caution is required to ensure that the else clause goes with the appropriate if statement. Consider the following lines:
if (i == j) if (j == k) System.out.println("i equals k"); else System.out.println("i doesn't equal j"); // WRONG!!
In this example, the inner if statement forms the single statement allowed by the syntax of the outer if statement. Unfortunately, it is not clear (except from the hint given by the indentation) which if the else goes with. And in this example, the indentation hint is wrong. The rule is that an else clause like this is associated with the nearest if statement. Properly indented, this code looks like this:
if (i == j) if (j == k) System.out.println("i equals k"); else System.out.println("i doesn't equal j"); // WRONG!!
This is legal code, but it is clearly not what the programmer had in mind. When working with nested if statements, you should use curly braces to make your code easier to read. Here is a better way to write the code:
if (i == j) { if (j == k) System.out.pri ntln("i equals k"); } else { System.out.println("i doesn't equal j"); }
The if/else statement is useful for testing a condition and choosing between two statements or blocks of code to execute. But what about when you need to choose between several blocks of code? This is typically done with an elseif clause, which is not really new syntax, but a common idiomatic usage of the standard if/else statement. It looks like this:
if (n == 1) { // Execute code block #1 } else if (n == 2) { // Execute code block #2 } else if (n == 3) { // Execute code block #3 } else { // If all else fails, execute block #4 }
There is nothing special about this code. It is just a series of if statements, where each if is part of the else clause of the previous statement. Using the elseif idiom is preferable to, and more legible than, writing these statements out in their fully nested form:
if (n == 1) { // Execute code block #1 } else { if (n == 2) { // Execute code block #2 } else { if (n == 3) { // Execute code block #3 } else { // If all else fails, execute block #4 } } }
An if statement causes a branch in the flow of a program's execution. You can use multiple if statements, as shown in the previous section, to perform a multiway branch. This is not always the best solution, however, especially whe the branches depend on the value of a single variable. In this case, it is inefficient to repeatedly check the value of the same variable in multiple if statements.
A better solution is to use a switch statement, which is inherited from the C programming language. Although the syntax of this statement is not nearly as elegant as other parts of Java, the brute practicality of the construct makes it worthwhile. If you are not familiar with the switch statement itself, you may at least be familiar with the basic concept, under the name computed goto or jump table. A switch statement has an integer expression and a body that contains various numbered entry points. The expression is evaluated, and control jumps to the entry point specified by that value. For example, the following switch statement is equivalent to the repeated if and else/if statements shown in the previous section:
switch(n) { case 1: // Start here if n == 1 // Execute code block #1 break; // Stop here case 2: // Start here if n == 2 // Execute code block #2 break; // Stop here case 3: // Start here if n == 3 // Execute code block #3 break; // Stop here default: // If all else fails... // Execute code block #4 break; // Stop here }
As you can see from the example, the various entry points into a switch statement are labeled either with the keyword case, followed by an integer value and a colon, or with the special default keyword, followed by a colon. When a switch statement executes, the interpreter computes the value of the expression in parentheses and then looks for a case label that matches that value. If it finds one, the interpreter starts executing the block of code at the first statement following the case label. If it does not find a case label with a matching value, the interpreter starts execution at the first statement following a special-case default: label. Or, if there is no default: label, the interpreter skips the body of the switch statement altogether.
Note the use of the break keyword at the end of each case in the previous code. The break statement is described later in this chapter, but, in this case, it causes the interpreter to exit the body of the switch statement. The case clauses in a switch statement specify only the starting point of the desired code. The individual cases are not independent blocks of code, and they do not have any implicit ending point. Therefore, you must explicitly specify the end of each case with a break or related statement. In the absence of break statements, a switch statement begins executing code at the first statement after the matching case label and continues executing statements until it reaches the end of the block. On rare occasions, it is useful to write code like this that falls through from one case label to the next, but 99% of the time you should be careful to end every case and default section with a statement ses the switch statement to stop executing. Normally you use a break statement, but return and throw also work.
A switch statement can have more than one case clause labeling the same statement. Consider the switch statement in the following method:
boolean parseYesOrNoResponse(char response) { switch(response) { case 'y': case 'Y': return true; case 'n': case 'N': return false; default: throw new IllegalArgumentException("Response must be Y or N"); } }
There are some important restrictions on the switch statement and its case labels. First, the expression associated with a switch statement must have a byte, char, short, or int value. The floating-point and boolean types are not supported, and neither is long, even though long is an integer type. Second, the value associated with each case label must be a constant value or a constant expression the compiler can evaluate. A case label cannot contain a runtime expressions involving variables or method calls, for example. Third, the case label values must be within the range of the data type used for the switch expression. And finally, it is obviously not legal to have two or more case labels with the same value or more than one default label.