by Joe Weber and Jay Cross
Controlling the flow of execution is perhaps the most important aspect of any programming language. Control flow allows you to direct the computer in different directions depending on conditions. So, if you’re lost, you might turn in a random direction. Otherwise, you would follow the map.
You can also think about control flow like a stop light. If the light is red, you want to stop your car, but if the light is green, you want all cars to go through the intersection. Without this type of decision-making, programs would be flat and lifeless. This chapter teaches you how to make the computer follow the map and traffic laws.
Almost all control flow expressions in Java control the flow based on a true or false value. For instance, as you learn later in this chapter, an if (value) statement causes the next statement to be executed only if the value is true. You can actually write something like if (true), but there is little value in it. Instead, usually the value is a boolean expression.
Operators in Java have particular meanings for use with boolean expressions. Many of the same operator symbols are used with other types of expressions. In most cases, the meanings are a natural extension from the operations performed on integer types. The operations shown in table 11.1 can be performed on Booleans.
Table 11.1 Operations on Boolean Expressions
Operation | Name | Description |
= | Assignment | As in tf = true; |
== | Equality | This produces a true if the two Boolean operands have the same value (true or false). It produces false otherwise. This is equivalent to not exclusive or (NXOR). |
!= | Inequality | This produces a true if the two Boolean operands have different values (one true, the other false). It produces false otherwise. This is equivalent to exclusive or (XOR). |
! | Logical NOT | If the operand is false, the output is true, and vice versa. |
& | AND | Produces a true if and only if both operands are true. |
| | OR | Produces a false if and only if both operands are false. |
[af] | XOR | Produces true only if exactly one
(exclusive OR) operand is true. |
&& | Logical AND | Same result for Booleans as described for &. |
|| | Logical OR | Same result for Booleans as described for |. |
?: | if-then-else | Requires a Boolean expression before the question mark. |
The most intuitive comparative operators are those that fall into a category known as relational operators. Relational operators include those standard greater-than and less-than symbols you learned about back in third grade. Conveniently enough, they work the same way as they did back in third grade, too. For instance, you know that if you write (3>4), you wrote something wrong (false). On the other hand (3<4) is correct (true). In Java and most other languages, you are not limited to evaluating constants; you are free to use variables, so the statement (Democrats> Republicans) is also valid. The complete list of relational operators is shown here:
The precedence of the relational operators is below that of the arithmetic operators, but above that of the assignment operator. Thus, the following two assignment statements produce identical results:
result1 = a+b < c*d ;
result2 = (a+b) < (c*d) ;
The associativity is left-to-right, but this feature isn't really very useful. It may not be immediately obvious why, but consider the following expression:
a < b < c
The first expression, a<b, is evaluated first, and produces a value of true or false. This value then would have to be compared to c. Because a boolean cannot be used in a relational expression, the compiler generates a syntax error.
RateArray [ day1 < day2 ]
NewValue = OldValue + ( NewRate > OldRate ) * Interest;
The equality operators are the next set of evaluation operators in Java. Equality operators enable you to compare one value to another and find out if they are equal. In third grade, you might have written this as (3=3). Unfortunately, in Java this statement would cause the compiler to use the assignment operator (also known as gets) rather than evaluate the equation. gets is used in traditional computing as a substitute for the = operator when reading text, as shown here. So, if you were to read out loud the line 3=3, you would say
The problem is that this is not the result you are looking for. To solve this problem, a separate two-character operator (==) is used. In Java then, you would write the equation as (3==3). This would be read out loud as:
On the other hand, obviously the equation (3==4) would result in an incorrect equation (false).
The following equality operators are very similar to the relational operators, with slightly lower precedence:
The equality operators can take operands of virtually any type. In the case of the primitive data types, the values of the operands are compared. However, if the operands are some other type of object (such as a class you created), the evaluation determines if both operands refer to exactly the same object. Consider the following example:
String1 == String2
In this example, String1 and String2 must refer to the same string—not to two different strings that happen to contain the same sequence of characters. Consider the next several lines:
String String1=“Hi Mom”;
String String2=“Hi Mom”;
//At this point String1
is not equal to String2
String String3=String1;
//Now string one is equal
to String2
Given this sequence, String1==String2 would return false, after the first two lines because, despite the fact that they contain the same letters, they are not the same object. On the other hand, String1=String3 would return true, because they refer to exactly the same object.
The associativity of these operators is again left-to-right. You've seen that the associativity of the relational operators is really not useful to you as a programmer. The associativity of the equality operators is only slightly more useful. Take a look at the following example:
StartTemp == EndTemp == LastRace
Here the variables StartTemp and EndTemp are compared first, and the boolean result of that comparison is compared to LastRace, which must be boolean. If LastRace is of some non-boolean type, the compiler generates an error
The third set of evaluation operators fall into a category known as logical expressions. Logical expressions work a bit differently than the previous operators, and are probably not something you covered in your third grade math class.
Logical expressions operate either on a pair of booleans, or on the individual bits of an object. There are two types of logical operators which are divided roughly along these lines:
You have already seen in Chapter 10, “Using Expressions,” how bitwise operators work. This chapter covers only the conditional half of the logical expression operators. However, it is interesting to note that, with some minor exceptions, bitwise operators and conditional operators will produce the same result if the operands are boolean.
There are two primary Boolean operators:
Oddly, in most computer languages, including Java, there is no Conditional-XOR operator.
These operators obey the same truth table that was constructed in Chapter 11, “Using Expressions,” for the bitwise operators. They also tend to be fairly easy to read. For instance, true && true when read “true and true” is obviously true. For your convenience, the truth tables for and and or are reproduced:
When A is | And when B is | (A && B) | (A || B) |
false | false | false | false |
false | true | false | true |
true | false | false | true |
true | true | true | true |
The operands of a logical-OR or a logical-AND expression are evaluated left-to-right; if the value of the expression is determined after evaluating the left-hand operand, the right-hand operand will not be evaluated. So, in the following example, if x is indeed less than y, then m and n are not compared:
(x<y) || (m>n)
If the left-hand side of this expression produces the boolean value true, then the result of the whole expression is true, regardless of the result of the comparison m>n. Note that, in the following expression, if you instead used a bitwise operator, m and n are compared regardless of the values of x and y:
(x<y) | (m>n)
The precedence of the two conditional operators is below that of the bitwise operators.
There are two unary logical operators:
By placing a negation operator in front of any value, the expression continues with the opposite value of that which the value had originally. For instance, !true would be false.
Both these operators have high precedence, equivalent to that of the other unary operators. Take a look at the following example, which shows a combination of the logical negation and the conditional-AND:
if (!dbase.EOF && dbase.RecordIsValid() )
Because the logical negation has high precedence, it is evaluated first. If EOF refers to End of File, you first check to see if you have reached the end of the file on this database. If you haven’t, then the second operand is evaluated, which in this case is a method invocation that might determine the validity of the record. The key to understanding this is to realize that if the first operand is false—in other words you have reached the end of the file—then you won’t check to see if the record is valid.
The conditional operator is the one ternary or triadic operator in Java, and operates as it does in C and C++. It takes the following form:
expression1 ? expression2 : expression3
In this syntax, expression1 must produce a Boolean value. If this value is true, then expression2 is evaluated, and its result is the value of the conditional. If expression1 is false, then expression3 is evaluated, and its result is the value of the conditional.
Consider the following examples. The first is using the conditional operator to determine the maximum of two values; the second is determining the minimum of two values; the third is determining the absolute value of a quantity:
BestReturn = Stocks > Bonds ? Stocks : Bonds ;
LowSales = JuneSales < JulySales ? JuneSales : JulySales ;
Distance = Site1-Site2 > 0 ? Site1-Site2 : Site2 - Site1 ;
In reviewing these examples, think about the precedence rules, and convince yourself that none of the three examples requires any brackets in order to be evaluated correctly.
Booleans (and Boolean expressions) are the only type that may be used in the true clause of the control flow statements as seen in the following code fragment:
boolean TestVal = false;
int IntVal = 1;
...
if (TestVal) {} else {}
if (IntVal != 1) {} else
{}
...
while (TestVal) {}
while (IntVal == 0) {}
...
do {} while (TestVal)
do {} while (IntVal == 0)
for (int j=0; TestVal; j++)
{}
for (int j=0; IntVal <
5; j++) {}
In this code fragment, the comparisons of the integer IntVal to an integer constant value are very simple boolean expressions. Naturally, much more complicated expressions could be used in the same place.
Control flow is the heart of any program. Control flow is the ability to adjust (control) the way that a program progresses (flows). By adjusting the direction that a computer takes, the programs that you build become dynamic. Without control flow, programs would not be able to do anything more than several sequential operations.
The simplest form of control flow is the if statement. An if takes a look at a conditional expression (probably derived through any of the means which are the first half of this chapter) and if the value is true, the next block of code is executed. The general syntax for the if is:
if (expression)
statement;
If the value is false, the computer skips the statement and continue on. An example of an if statement is shown in the following code fragment:
if (myNameIsFred)
System.out.println(“Hi Fred”);
System.out.println(“Welcome
to the system”);
When this fragment runs, if the value of myNameIsFred is true, the computer prints out the following:
Hi Fred
Welcome to the system
However, if the value is false, the program skips over the line after the if and the result is:
Welcome to the system
In most situations, you will want to execute more than one line of code based on an evaluation. To do this, you can place a code block after the if, which begins and end with a pair of braces: {,}. The following code fragment shows just such an example.
if (umpire.says.equals(“Strike
two”)){ //equals method returns boolean
Crowd.cry(“Fraud94");
// method call
Strike++; // last statement
in if block.
}
Casey.face(“Christian charity”);
// 1st statement after if block.
Only slightly more advanced than a simple if, the if-else expression passes execution to the else statement if the if evaluates to false. The code in the else block is not run if the if is true. Only one or the other set of code is run. The general syntax for an if-else is:
if (expression)
if_statement;
else
else_statement;
An example of an if-else statement is:
if (strike != 2)
Casey.lip(“Curling Sneer”); // single substatement (could have been
a block)
else {
Casey.teeth(“Clenched in
hate”); // block of substatements (could have been single)
Casey.bat.pound(“Plate”);
}
One important aspect of if-else blocks is how else blocks are evaluated when there are nested ifs. In other words, consider the following code:
if (firstVal==0)
if (secondVal==1)
firstVal++;
else
firstVal--;
When is the else executed? In this example, the tabbing shows you that the else is associated with the inner (second) if. An if-else expression counts as one statement, so the else belongs to the most recent if, and is part of the if statement for the first if. Another way to put this is that ifs are evaluated to elses in a First In First Out (FIFO) fashion. You can change this by placing the second if in a block:
if (firstVal==0){
if (secondVal==1)
firstVal++;
}
else
firstVal--;
Because a block counts as a single statement, the else is associated with the first if.
Another equally valid if-else statement is known as the compound if:
if (firstVal==0)
if (secondVal==1)
firstVal++;
else if (thirdVal==2)
firstVal--;
In this example, the firstVal-- statement is only executed when firstVal is 0, secondVal is not 1, and the thirdVal is 2. Follow this last example through to verify to yourself that this is the case.
Programmers use iteration statements to control sequences of statements that are repeated according to runtime conditions.
Java supports five types of iteration statements:
These are very similar to the statements of the same type found in C and C++, with the exception that continue and break statements in Java have optional parameters that can change their behavior (compared with C and C++, where these statements have no parameters) within the substatement blocks.
The while statement tests an expression, and if it is true, executes the next statement or block repeatedly until the expression becomes false. When the variable or expression is false, control is passed to the next statement after the while statement. The syntax for a while loop looks very similar to that of an if statement:
while (expression)
statement;
Obviously, while loops can become endless either intentionally, or by accident if the expression is made so that it will never become false. The following example shows a while loop in action:
while (Casey.RoundingTheBasepads==true)
{
Crowd.cry(“Hooray for Casey”);
}
In this example, it is clear that the expression might not be true initially, and if not, the block in the sub-statement will never be executed. If it is true, this block of code is executed repeatedly until it is not true.
The do statement is similar to the while statement. In fact, it has a while clause at the end. Like the while expression in the previous section, the expression in the while statement must be a boolean. The execution of a do loop processes the statement, and then evaluates the while. If the while is true, execution returns to the do statement until the expression becomes false. The complete syntax for a do-while loop is:
do
statement;
while (expression)
The primary reason why a programmer chooses to use a do statement instead of a while statement is the statement will always be executed at least once, regardless of the value of the expression. This is also known as post-evaluation.
do {
Crowd.cry(“Kill the Umpire!”);
} while (umpire.says.equals(“Strike
two”));
In this example, the method Crowd.cry is invoked at least once no matter what. But as long as the umpire.says method returns the string “Strike two”, the Crowd.cry method is called over and over again.
The most complicated of the four iteration statements is the for loop. The for statement gives the programmer the capability of all three of the other iteration statements. The complete syntax of a for loop is:
for (initialization,
expression , step )
statement;
The for loop first runs the initialization code (like a do) and then evaluates the expression (like an if or while). If the expression is true, the statement is executed, and then the step is performed. A for loop can also be written with a while loop as follows:
initialization;
while (expression){
statement;
step;
}
An example of a for loop appears in the following code fragment:
for (int ball=0, int strike=0;
(ball<4) && (strike<3);Ump.EvaluateSwing()) {
Pitcher.pitch();
Player.swing();
}
This example demonstrates the fact that the initialization clause can have more than one statement, and that the statements are separated by commas (,). Both the initialization and step can have multiple statements this way. On the flip side, the statements can also be empty, with no statements.
The next type of control flow is the switch statement. The switch statement is the first control flow statement that does not require a boolean evaluation. A switch passes control to one of many statements within its block of sub-statements, depending on the value of the expression in the statement. Control is passed to the first statement following a case label with the same value as the expression. If there are none, control passes to the default label. If there is no default label, control passes to the first statement after the switch block.
The syntax for a switch is as follows:
switch (expression){
case V1: statement1;
break;
case V2: statement2;
break;
default: statementD;
}
Unique to switches the expression must be of an integer type. You may use bytes, shorts, chars, or ints, but not floats or booleans.
The break statements are not really required. However, because of the way a switch works, breaks frequently end up being used. You see, as soon as a value matches the expression, execution continues from that point. The execution falls through all the other statements. Take a look at the following example:
switch (1){
case 1: System.out.println
(“one”);
case 2: System.out.println
(“two”);
case default: System.out.println(“Default”);
}
In this example, the resulting output would be:
one
two
Default
This happens because as soon as a case match is made, the execution falls through, or continues, through to the end of the switch. It is likely, however, that you don’t want to print all three results. The break can be used to only produce the one printout. To do this the code should be changed to:
switch (1){
case 1: System.out.println
(“one”);
break;
case 2: System.out.println
(“two”);
break;
case default: System.out.println(“Default”);
break;
}
The switch expression and case label constants must all evaluate to either byte, short, char, or int. In addition, no two case labels in the same switch block can have the same value.
Another example of the switch statement is included in the following code fragment:
switch (strike) {
case 0:
case 1:
Casey.lip(“Curling Sneer”);
break;
case 2:
Casey.teeth(“Clenched in
hate”);
Casey.bat.pound(“Plate”);
break;
default:
System.out.println(“Strike
out of range”);
}
In this example, assume that strike is a compatible integer type (for example, int). Control passes to the correct line, depending on the value of strike. If strike doesn’t have one of the values it should have, a programmer-defined error message is printed.
In addition to the more common control flow functions, Java also has three kinds of jump statements : break, continue, and return.
The sub-statement blocks of loops and switch statements can be broken out of by using the break statement. An unlabeled break statement passes control to the next line after the current (innermost) iteration (while, do, for, or switch statement).
With a label, control may be passed to a statement with that label within the current method. If there is a finally clause to a currently open try statement, that clause is executed before control is passed on.
A continue statement may only appear within the sub-statement block of an iteration statement (while, do, or for). The effect of the unlabeled continue statement is to skip the remainder of the statements in the innermost iteration statement’s block, and go on to the next pass through the loop. The label parameter permits the programmer to choose which level of nested iteration statements to continue with.
If there is a finally clause for a currently open try statement within the indicated level of nesting, that clause is executed before control is passed on.
A return statement passes control to the caller of the method, constructor, or static initializer containing the return statement. If the return statement is in a method that is not declared void, it may have a parameter of the same type as the method.
If there is a finally clause for a currently open try statement, that clause is executed before control is passed.
| Previous Chapter | Next Chapter |
| Que Home Page | Digital Bookshelf | Disclaimer |
To order books from QUE, call us at 800-716-0044 or 317-361-5400.
For comments or technical support for our books and software, select Talk to Us.
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.