Objectives
In this chapter you’ll learn:
• To use the if
and if
...else
selection statements to choose among alternative actions.
• To use the while
repetition statement to execute statements in a program repeatedly.
• To use counter-controlled repetition and sentinel-controlled repetition.
• To use the compound assignment, increment and decrement operators.
• To use the primitive data types.
Let’s all move one place on.
—Lewis Carroll
The wheel is come full circle.
—William Shakespeare
How many apples fell on Newton’s head before he took the hint!
—Robert Frost
All the evolution we know of proceeds from the vague to the definite.
—Charles Sanders Peirce
Outline
4.1 Introduction
4.2 Control Structures
4.3 if
Single-Selection Statement
4.4 if
...else
Double-Selection Statement
4.5 while
Repetition Statement
4.6 Counter-Controlled Repetition
4.7 Sentinel-Controlled Repetition
4.8 Nested Control Statements
4.9 Compound Assignment Operators
4.10 Increment and Decrement Operators
4.11 Primitive Types
4.12 (Optional) Software Engineering Case Study: Identifying Class Attributes
4.13 Wrap-Up
In this chapter and in Chapter 5, Control Statements: Part 2, we present the theory and principles of structured programming. The concepts presented here are crucial in building classes and manipulating objects.
In this chapter, we introduce Java’s if
...else
and while
statements. We devote a portion of this chapter (and Chapters 5 and 7) to further developing the GradeBook
class introduced in Chapter 3. In particular, we add a method to the GradeBook
class that uses control statements to calculate the average of a set of student grades. Another example demonstrates additional ways to combine control statements to solve a similar problem. We introduce Java’s compound assignment operators and explore Java’s increment and decrement operators. These additional operators abbreviate and simplify many program statements. Finally, overview Java’s primitive data types.
Normally, statements in a program are executed one after the other in the order in which they are written. This process is called sequential execution. Various Java statements, which we’ll soon discuss, enable you to specify that the next statement to execute is not necessarily the next one in sequence. This is called transfer of control.
During the 1960s, it became clear that the indiscriminate use of transfers of control was the root of much difficulty experienced by software development groups. The blame was pointed at the goto
statement (used in most programming languages of the time), which allows the programmer to specify a transfer of control to one of a very wide range of possible destinations in a program. The notion of so-called structured programming became almost synonymous with “goto
elimination.” [Note: Java does not have a goto
statement; however, the word goto
is reserved by Java and should not be used as an identifier in programs.]
Bohm and Jacopini’s[1] work demonstrated that all programs could be written in terms of only three control structures—the sequence structure, the selection structure and the repetition structure. The term “control structures” comes from the field of computer science—when we introduce Java’s implementations of control structures, we’ll refer to them in the terminology of the Java Language Specification as “control statements.”
[1]Bohm, C., and G. Jacopini, “Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules,” Communications of the ACM, Vol. 9, No. 5, May 1966, pp. 336–371.
The sequence structure is built into Java. Unless directed otherwise, Java statements execute one after the other in the order in which they are written—that is, in sequence. The activity diagram in Fig. 4.1 illustrates a typical sequence structure in which two calculations are performed in order. Java lets us have as many actions as we want in a sequence structure. As we’ll soon see, anywhere a single action may be placed, we may place several actions in sequence.
Fig. 4.1. Sequence structure activity diagram.
Activity diagrams are part of the UML. An activity diagram models the workflow (also called the activity) of a portion of a software system. Such workflows may include a portion of an algorithm, such as the sequence structure in Fig. 4.1. Activity diagrams are composed of special-purpose symbols, such as action-state symbols (rectangles with their left and right sides replaced with arcs curving outward), diamonds and small circles. These symbols are connected by transition arrows, which represent the flow of the activity—that is, the order in which the actions should occur.
Activity diagrams help programmers develop and represent algorithms. Consider the activity diagram for the sequence structure in Fig. 4.1. It contains two action states that represent actions to perform. Each action state contains an action expression—for example, “add grade to total” or “add 1 to counter”—that specifies a particular action to perform. Other actions might include calculations or input/output operations. The arrows in the activity diagram represent transitions, which indicate the order in which the actions represented by the action states occur. The program that implements the activities illustrated by the diagram in Fig. 4.1 first adds grade
to total
, then adds 1
to counter
.
The solid circle located at the top of the activity diagram represents the activity’s initial state—the beginning of the workflow before the program performs the modeled actions. The solid circle surrounded by a hollow circle that appears at the bottom of the diagram represents the final state—the end of the workflow after the program performs its actions.
Figure 4.1 also includes rectangles with the upper-right corners folded over. These are UML notes (like comments in Java)—explanatory remarks that describe the purpose of symbols in the diagram. Figure 4.1 uses UML notes to show the Java code associated with each action state in the activity diagram. A dotted line connects each note with the element that the note describes. Activity diagrams normally do not show the Java code that implements the activity. We use notes for this purpose here to illustrate how the diagram relates to Java code. For more information on the UML, see our optional case study, which appears in the Software Engineering Case Study sections at the ends of Chapters 1–8 and 10, or visit www.uml.org.
Java has three types of selection statements (discussed in this chapter and Chapter 5). The if
statement either performs (selects) an action, if a condition is true, or skips it, if the condition is false. The if
...else
statement performs an action if a condition is true and performs a different action if the condition is false. The switch
statement (Chapter 5) performs one of many different actions, depending on the value of an expression.
The if
statement is a single-selection statement because it selects or ignores a single action (or, as we’ll soon see, a single group of actions). The if
...else
statement is called a double-selection statement because it selects between two different actions (or groups of actions). The switch
statement is called a multiple-selection statement because it selects among many different actions (or groups of actions).
Java provides three repetition statements that enable programs to perform statements repeatedly as long as a condition (called the loop-continuation condition) remains true. The repetition statements are the while
, do
...while
and for
statements. (Chapter 5 presents the do
...while
and for
statements.) The while
and for
statements perform the action (or group of actions) in their bodies zero or more times—if the loop-continuation condition is initially false, the action (or group of actions) will not execute. The do
...while
statement performs the action (or group of actions) in its body one or more times.
The words if
, else
, switch
, while
, do
and for
are Java keywords. Keywords cannot be used as identifiers, such as variable names. A complete list of Java keywords appears in Appendix C.
Java has only three kinds of control structures, which from this point forward we refer to as control statements: the sequence statement, selection statements (three types) and repetition statements (three types). Every program is formed by combining as many sequence, selection and repetition statements as is appropriate for the algorithm the program implements. As with the sequence statement in Fig. 4.1, we can model each control statement as an activity diagram. Each diagram contains an initial state and a final state that represent a control statement’s entry point and exit point, respectively. Single-entry/single-exit control statements make it easy to build programs—we “attach” the control statements to one another by connecting the exit point of one to the entry point of the next. We call this control-statement stacking. We’ll learn that there is only one other way in which control statements may be connected—control-statement nesting—in which one control statement appears inside another. Thus, Java programs are constructed from only three kinds of control statements, combined in only two ways. This is the essence of simplicity.
if
Single-Selection StatementPrograms use selection statements to choose among alternative courses of action. For example, suppose that the passing grade on an exam is 60. The statement
if ( studentGrade >= 60 )
System.out.println( "Passed" );
determines whether the condition “studentGrade >= 60” is true or false. If it is true, "Passed"
is printed, and the next statement in order is “performed.” If the condition is false, the println
statement is ignored, and the next statement in order is performed.
Figure 4.2 illustrates the single-selection if
statement. This figure contains what is perhaps the most important symbol in an activity diagram—the diamond, or decision symbol, which indicates that a decision is to be made. The workflow will continue along a path determined by the symbol’s associated guard conditions, each of which can be true or false. Each transition arrow emerging from a decision symbol has a guard condition (specified in square brackets next to the transition arrow). If a guard condition is true, the workflow enters the action state to which the transition arrow points. In Fig. 4.2, if the grade is greater than or equal to 60, the program prints “Passed
,” then transitions to the final state of this activity. If the grade is less than 60, the program immediately transitions to the final state without displaying a message.
Fig. 4.2. if
single-selection statement UML activity diagram.
The if
statement is a single-entry/single-exit control statement. We’ll see that the activity diagrams for the remaining control statements also contain initial states, transition arrows, action states that indicate actions to perform, decision symbols (with associated guard conditions) that indicate decisions to be made and final states.
if
...else
Double-Selection StatementThe if
single-selection statement performs an indicated action only when the condition is true
; otherwise, the action is skipped. The if
...else
double-selection statement allows the programmer to specify an action to perform when the condition is true and a different action when the condition is false. For example, the statement
if ( grade >= 60 )
System.out.println( "Passed" );
else
System.out.println( "Failed" );
prints "Passed"
if the student’s grade is greater than or equal to 60, but prints "Failed"
if it is less than 60. In either case, after printing occurs, the next pseudocode statement in sequence is “performed.”
Note that the body of the else
is also indented. Whatever indentation convention you choose should be applied consistently throughout your programs.
Figure 4.3 illustrates the flow of control in the if
...else
statement. Once again, the symbols in the UML activity diagram (besides the initial state, transition arrows and final state) represent action states and decisions. We continue to emphasize this action/decision model of computing. Imagine again a deep bin containing as many empty if
...else
statements as might be needed to build any Java program. Your job is to assemble these if
...else
statements (by stacking and nesting) with any other control statements required by the algorithm. You fill in the action states and decision symbols with action expressions and guard conditions appropriate to the algorithm you are developing.
Fig. 4.3. if
...else
double-selection statement UML activity diagram.
?:
)Java provides the conditional operator (?:
)
that can be used in place of an if
...else
statement. This is Java’s only ternary operator—this means that it takes three operands. Together, the operands and the ?:
symbol form a conditional expression. The first operand (to the left of the ?
) is a boolean
expression (i.e., a condition that evaluates to a boolean
value—true
or false
), the second operand (between the ?
and :
) is the value of the conditional expression if the boolean
expression is true
and the third operand (to the right of the :
) is the value of the conditional expression if the boolean
expression evaluates to false
. For example, the statement
System.out.println( studentGrade >= 60 ? "Passed" : "Failed" );
prints the value of println
’s conditional-expression argument. The conditional expression in this statement evaluates to the string "Passed"
if the boolean
expression studentGrade >= 60
is true and evaluates to the string "Failed"
if the boolean
expression is false. Thus, this statement with the conditional operator performs essentially the same function as the if
...else
statement shown earlier in this section. The precedence of the conditional operator is low, so the entire conditional expression is normally placed in parentheses. We’ll see that conditional expressions can be used in some situations where if
...else
statements cannot.
Conditional expressions are more difficult to read than if
...else
statements and should be used to replace only simple if
...else
statements that choose between two values.
if
...else
StatementsA program can test multiple cases by placing if
...else
statements inside other if
...else
statements to create nested if
...else
statements. For example, the following nested if
...else
prints A
for exam grades greater than or equal to 90, B
for grades in the range 80 to 89, C
for grades in the range 70 to 79, D
for grades in the range 60 to 69 and F
for all other grades:
If studentGrade
is greater than or equal to 90, the first four conditions will be true, but only the statement in the if
-part of the first if
...else
statement will execute. After that statement executes, the else
-part of the “outermost” if
...else
statement is skipped. Most Java programmers prefer to write the preceding if
...else
statement as
The two forms are identical except for the spacing and indentation, which the compiler ignores. The latter form is popular because it avoids deep indentation of the code to the right. Such indentation often leaves little room on a line of code, forcing lines to be split and decreasing program readability.
else
ProblemThe Java compiler always associates an else
with the immediately preceding if
unless told to do otherwise by the placement of braces ({
and }
). This behavior can lead to what is referred to as the dangling-else
problem. For example,
appears to indicate that if x
is greater than 5
, the nested if
statement determines whether y
is also greater than 5
. If so, the string "x and y are > 5"
is output. Otherwise, it appears that if x
is not greater than 5
, the else
part of the if
...else
outputs the string "x is <= 5"
.
Beware! This nested if
...else
statement does not execute as it appears. The compiler actually interprets the statement as
in which the body of the first if
is a nested if
...else
. The outer if
statement tests whether x
is greater than 5
. If so, execution continues by testing whether y
is also greater than 5
. If the second condition is true, the proper string—"x and y are > 5"
—is displayed. However, if the second condition is false, the string "x is <= 5"
is displayed, even though we know that x
is greater than 5
. Equally bad, if the outer if
statement’s condition is false, the inner if
...else
is skipped and nothing is displayed.
To force the nested if
...else
statement to execute as it was originally intended, we must write it as follows:
The braces ({}
) indicate to the compiler that the second if
statement is in the body of the first if
and that the else
is associated with the first if
.
The if
statement normally expects only one statement in its body. To include several statements in the body of an if
(or the body of an else
for an if
...else
statement), enclose the statements in braces ({
and }
). A set of statements contained within a pair of braces is called a block. A block can be placed anywhere in a program that a single statement can be placed.
The following example includes a block in the else
-part of an if
...else
statement:
In this case, if grade
is less than 60, the program executes both statements in the body of the else
and prints
Failed.
You must take this course again.
Note the braces surrounding the two statements in the else
clause. These braces are important. Without the braces, the statement
System.out.println( "You must take this course again." );
would be outside the body of the else
-part of the if
...else
statement and would execute regardless of whether the grade was less than 60.
Common Programming Error 4.1
Forgetting one or both of the braces that delimit a block can lead to syntax errors or logic errors in a program.
Good Programming Practice 4.2
Always using braces in an if
...else
(or other) statement helps prevent their accidental omission, especially when adding statements to the if
-part or the else
-part at a later time. To avoid omitting one or both of the braces, some programmers type the beginning and ending braces of blocks before typing the individual statements within the braces.
Just as a block can be placed anywhere a single statement can be placed, it is also possible to have an empty statement. Recall from Section 2.7 that the empty statement is represented by placing a semicolon (;
) where a statement would normally be.
Common Programming Error 4.2
Placing a semicolon after the condition in an if
or if
...else
statement leads to a logic error in single-selection if
statements and a syntax error in double-selection if
...else
statements (when the if
-part contains an actual body statement).
while
Repetition StatementA repetition statement allows the programmer to specify that a program should repeat an action while some condition remains true. As an example of Java’s while
repetition statement, consider a program segment designed to find the first power of 3 larger than 100. Suppose that the int
variable product
is initialized to 3
. When the following while
statement finishes executing, product
contains the result:
When this while
statement begins execution, the value of variable product
is 3. Each iteration of the while
statement multiplies product
by 3, so product
takes on the values 9, 27, 81 and 243 successively. When variable product
becomes 243, the while
statement condition—product <= 100
—becomes false. This terminates the repetition, so the final value of product
is 243. At this point, program execution continues with the next statement after the while
statement.
Not providing, in the body of a while
statement, an action that eventually causes the condition in the while
to become false normally results in an infinite loop.
The UML activity diagram in Fig. 4.4 illustrates the flow of control that corresponds to the preceding while
statement. Once again, the symbols in the diagram (besides the initial state, transition arrows, a final state and three notes) represent an action state and a decision. This diagram also introduces the UML’s merge symbol. The UML represents both the merge symbol and the decision symbol as diamonds. The merge symbol joins two flows of activity into one. In this diagram, the merge symbol joins the transitions from the initial state and from the action state, so they both flow into the decision that determines whether the loop should begin (or continue) executing. The decision and merge symbols can be distinguished by the number of “incoming” and “outgoing” transition arrows. A decision symbol has one transition arrow pointing to the diamond and two or more transition arrows pointing out from the diamond to indicate possible transitions from that point. In addition, each transition arrow pointing out of a decision symbol has a guard condition next to it. A merge symbol has two or more transition arrows pointing to the diamond and only one transition arrow pointing from the diamond, to indicate multiple activity flows merging to continue the activity. None of the transition arrows associated with a merge symbol has a guard condition.
Fig. 4.4. while
repetition statement UML activity diagram.
Figure 4.4 clearly shows the repetition of the while
statement discussed earlier in this section. The transition arrow emerging from the action state points back to the merge, from which program flow transitions back to the decision that is tested at the beginning of each iteration of the loop. The loop continues to execute until the guard condition product > 100
becomes true. Then the while
statement exits (reaches its final state), and control passes to the next statement in sequence in the program.
Consider the following problem statement:
A class of ten students took a quiz. The grades (integers in the range 0 to 100) for this quiz are available to you. Determine the class average on the quiz.
The class average is equal to the sum of the grades divided by the number of students. The program for solving this problem must input each grade, keep track of the total of all grades input, perform the averaging calculation and print the result.
GradeBook
Class GradeBook
(Fig. 4.5) contains a constructor (lines 11–14) that assigns a value to the class’s instance variable courseName
(declared in line 8). Lines 17–20, 23–26 and 29–34 declare methods setCourseName
, getCourseName
and displayMessage
, respectively. Lines 37–66 declare method determineClassAverage
, which implements the class-averaging algorithm.
Fig. 4.5. Counter-controlled repetition: Class-average problem.
Line 40 declares and initializes Scanner
variable input
, which is used to read values entered by the user. Lines 42–45 declare local variables total
, gradeCounter
, grade
and average
to be of type int
. Variable grade
stores the user input.
Note that the declarations (in lines 42–45) appear in the body of method determineClassAverage
. Recall that variables declared in a method body are local variables and can be used only from the line of their declaration in the method to the closing right brace (}
) of the method declaration. A local variable’s declaration must appear before the variable is used in that method and cannot be accessed outside the method in which it is declared.
In the versions of class GradeBook
in this chapter, we simply read and process a set of grades. The averaging calculation is performed in method determineClassAverage
using local variables—we do not preserve any information about student grades in instance variables of the class. In later versions of the class (in Chapter 7, Arrays), we maintain the grades in memory using an instance variable that refers to a data structure known as an array. This allows a GradeBook
object to perform various calculations on the same set of grades without requiring the user to enter the grades multiple times.
The assignments (in lines 48–49) initialize total
to 0
and gradeCounter
to 1
. Note that these initializations occur before the variables are used in calculations. Variables grade
and average
(for the user input and calculated average, respectively) need not be initialized here—their values will be assigned as they are input or calculated later in the method.
Reading the value of a local variable before it is initialized results in a compilation error. All local variables must be initialized before their values are read in expressions.
Line 52 indicates that the while
statement should continue iterating as long as the value of gradeCounter
is less than or equal to 10. While this condition remains true, the while
statement repeatedly executes the statements between the braces that delimit its body (lines 54–57).
Line 54 displays the prompt "Enter grade: "
. Line 55 reads the grade entered by the user and assigns it to variable grade
. Then line 56 adds the new grade
entered by the user to the total
and assigns the result to total
, which replaces its previous value.
Line 57 adds 1
to gradeCounter
to indicate that the program has processed a grade and is ready to input the next grade from the user. Incrementing gradeCounter
eventually causes gradeCounter
to exceed 10. At that point the while
loop terminates because its condition (line 52) becomes false.
When the loop terminates, line 61 performs the averaging calculation and assigns its result to the variable average
. Line 64 uses System.out
’s printf
method to display the text "Total of all 10 grades is "
followed by variable total
’s value. Line 65 then uses printf
to display the text "Class average is "
followed by variable average
’s value. After reaching line 66, method determineClassAverage
returns control to the calling method (i.e., main
in GradeBookTest
of Fig. 4.6).
Fig. 4.6. GradeBookTest
class creates an object of class GradeBook
(Fig. 4.5) and invokes its determineClassAverage
method.
GradeBookTest
Class GradeBookTest
(Fig. 4.6) creates an object of class GradeBook
(Fig. 4.5) and demonstrates its capabilities. Lines 10–11 of Fig. 4.6 create the GradeBook
object and assign it to variable myGradeBook
. The String
in line 11 is passed to the GradeBook
constructor (lines 11–14 of Fig. 4.5). Line 13 calls myGradeBook
’s displayMessage
method to display a welcome message to the user. Line 14 then calls myGradeBook
’s determineClassAverage
method to allow the user to enter 10 grades, then calculates and prints the average.
The averaging calculation performed by method determineClassAverage
in response to the method call at line 14 in Fig. 4.6 produces an integer result. The program’s output indicates that the sum of the grade values in the sample execution is 846, which, when divided by 10, should yield the floating-point number 84.6. However, the result of the calculation total / 10
(line 61 of Fig. 4.5) is the integer 84, because total
and 10
are both integers. Dividing two integers results in integer division—any fractional part of the calculation is lost (i.e., truncated). We’ll see how to obtain a floating-point result from the averaging calculation in the next section.
Common Programming Error 4.5
Assuming that integer division rounds (rather than truncates) can lead to incorrect results. For example, 7 ÷ 4, which yields 1.75 in conventional arithmetic, truncates to 1 in integer arithmetic, rather than rounding to 2.
Let us generalize Section 4.6’s class-average problem. Consider the following problem:
Develop a class-averaging program that processes grades for an arbitrary number of students each time it is run.
In the previous class-average example, the problem statement specified the number of students, so the number of grades (10) was known in advance. In this example, no indication is given of how many grades the user will enter during the program’s execution. The program must process an arbitrary number of grades. How can it determine when to stop the input of grades? How will it know when to calculate and print the class average?
One way to solve this problem is to use a special value called a sentinel value (also called a signal value, a dummy value or a flag value) to indicate “end of data entry.” The user enters grades until all legitimate grades have been entered. The user then types the sentinel value to indicate that no more grades will be entered. Sentinel-controlled repetition is often called indefinite repetition because the number of repetitions is not known before the loop begins executing.
Clearly, a sentinel value must be chosen that cannot be confused with an acceptable input value. Grades on a quiz are nonnegative integers, so –1 is an acceptable sentinel value for this problem. Thus, a run of the class-average program might process a stream of inputs such as 95, 96, 75, 74, 89 and –1. The program would then compute and print the class average for the grades 95, 96, 75, 74 and 89; since –1 is the sentinel value, it should not enter into the averaging calculation.
Common Programming Error 4.6
Choosing a sentinel value that is also a legitimate data value is a logic error.
GradeBook
Figure 4.7 shows the Java class GradeBook
containing method determineClassAverage
that implements the sentinel-controlled repetition solution to the calss averaging problem. Although each grade is an integer, the averaging calculation is likely to produce a number with a decimal point—in other words, a real (i.e., floating-point) number. The type int
cannot represent such a number, so this class uses type double
to do so.
Fig. 4.7. Sentinel-controlled repetition: Class-average problem.
In this example, we see that control statements may be stacked on top of one another (in sequence). The while
statement (lines 57–65) is followed in sequence by an if
...else
statement (lines 69–80). Much of the code in this program is identical to that in Fig. 4.5, so we concentrate on the new concepts.
Line 45 declares double
variable average
, which allows us to store the calculated class average as a floating-point number. Line 49 initializes gradeCounter
to 0
, because no grades have been entered yet. Remember that this program uses sentinel-controlled repetition to input the grades from the user. To keep an accurate record of the number of grades entered, the program increments gradeCounter
only when the user enters a valid grade value.x
Compare the program logic for sentinel-controlled repetition in this application with that for counter-controlled repetition in Fig. 4.5. In counter-controlled repetition, each iteration of the while
statement (e.g., lines 52–58 of Fig. 4.5) reads a value from the user, for the specified number of iterations. In sentinel-controlled repetition, the program reads the first value (lines 53–54 of Fig. 4.7) before reaching the while
. This value determines whether the program’s flow of control should enter the body of the while
. If the condition of the while
is false, the user entered the sentinel value, so the body of the while
does not execute (i.e., no grades were entered). If, on the other hand, the condition is true, the body begins execution, and the loop adds the grade
value to the total
(line 59). Then lines 63–64 in the loop body input the next value from the user. Next, program control reaches the closing right brace (}
) of the loop body at line 65, so execution continues with the test of the while
’s condition (line 57). The condition uses the most recent grade
input by the user to determine whether the loop body should execute again. Note that the value of variable grade
is always input from the user immediately before the program tests the while
condition. This allows the program to determine whether the value just input is the sentinel value before the program processes that value (i.e., adds it to the total
). If the sentinel value is input, the loop terminates, and the program does not add –1 to the total
.
Good Programming Practice 4.3
In a sentinel-controlled loop, the prompts requesting data entry should explicitly remind the user of the sentinel value.
After the loop terminates, the if
...else
statement at lines 69–80 executes. The condition at line 69 determines whether any grades were input. If none were input, the else
part (lines 79–80) of the if
...else
statement executes and displays the message "No grades were entered"
and the method returns control to the calling method.
Error-Prevention Tip 4.1
When performing division by an expression whose value could be zero, explicitly test for this possibility and handle it appropriately in your program (e.g., by printing an error message) rather than allow the error to occur.
Notice the while
statement’s block in Fig. 4.7 (lines 58–65). Without the braces, the loop would consider its body to be only the first statement, which adds the grade
to the total
. The last three statements in the block would fall outside the loop body, causing the computer to interpret the code incorrectly as follows:
while ( grade != -1 )
total = total + grade; // add grade to total
gradeCounter = gradeCounter + 1; // increment counter
// prompt for input and read next grade from user
System.out.print( "Enter grade or -1 to quit: " );
grade = input.nextInt();
The preceding code would cause an infinite loop in the program if the user did not input the sentinel -1
at line 54 (before the while
statement).
Common Programming Error 4.7
Omitting the braces that delimit a block can lead to logic errors, such as infinite loops. To prevent this problem, some programmers enclose the body of every control statement in braces, even if the body contains only a single statement.
If at least one grade was entered, line 72 of Fig. 4.7 calculates the average of the grades. Recall from Fig. 4.5 that integer division yields an integer result. Even though variable average
is declared as a double
(line 45), the calculation
average = total / gradeCounter;
loses the fractional part of the quotient before the result of the division is assigned to average
. This occurs because total
and gradeCounter
are both integers, and integer division yields an integer result. To perform a floating-point calculation with integer values, we must temporarily treat these values as floating-point numbers for use in the calculation. Java provides the unary cast operator to accomplish this task. Line 72 uses the (
double
)
cast operator—a unary operator—to create a temporary floating-point copy of its operand total
(which appears to the right of the operator). Using a cast operator in this manner is called explicit conversion. The value stored in total
is still an integer.
The calculation now consists of a floating-point value (the temporary double
version of total
) divided by the integer gradeCounter
. Java knows how to evaluate only arithmetic expressions in which the operands’ types are identical. To ensure that the operands are of the same type, Java performs an operation called promotion (or implicit conversion) on selected operands. For example, in an expression containing values of the types int
and double
, the int
values are promoted to double
values for use in the expression. In this example, the value of gradeCounter
is promoted to type double
, then the floating-point division is performed and the result of the calculation is assigned to average
. As long as the (double)
cast operator is applied to any variable in the calculation, the calculation will yield a double
result. Later in this chapter, we discuss all the primitive types. You’ll learn more about the promotion rules in Section 6.7.
Common Programming Error 4.8
The cast operator can be used to convert between primitive numeric types, such as int
and double
, and between related reference types (as we discuss in Chapter 10, Object-Oriented Programming: Polymorphism). Casting to the wrong type may cause compilation or runtime errors.
Cast operators are available for any type. The cast operator is formed by placing parentheses around the name of a type. The operator is a unary operator (i.e., an operator that takes only one operand). In Chapter 2, we studied the binary arithmetic operators. Java also supports unary versions of the plus (+
) and minus (–
) operators, so the programmer can write expressions like -7
or +5
. Cast operators associate from right to left and have the same precedence as other unary operators, such as unary +
and unary -
. This precedence is one level higher than that of the multiplicative operators *
, /
and %
. (See the operator precedence chart in Appendix A.) We indicate the cast operator with the notation (
type)
in our precedence charts, to indicate that any type name can be used to form a cast operator.
Line 77 outputs the class average using System.out
’s printf
method. In this example, we display the class average rounded to the nearest hundredth. The format specifier %.2f
in printf
’s format control string (line 77) indicates that variable average
’s value should be displayed with two digits of precision to the right of the decimal point—indicated by.2
in the format specifier. The three grades entered during the sample execution of class GradeBookTest
(Fig. 4.8) total 257, which yields the average 85.666666.... Method printf
uses the precision in the format specifier to round the value to the specified number of digits. In this program, the average is rounded to the hundredths position and the average is displayed as 85.67
.
Fig. 4.8. GradeBookTest
class creates an object of class GradeBook
(Fig. 4.7) and invokes its determineClassAverage
method.
We have seen that control statements can be stacked on top of one another (in sequence). In this case study, we examine the only other structured way control statements can be connected, namely, by nesting one control statement within another.
Consider the following problem statement:
A college offers a course that prepares students for the state licensing exam for real estate brokers. Last year, ten of the students who completed this course took the exam. The college wants to know how well its students did on the exam. You have been asked to write a program to summarize the results. You have been given a list of these 10 students. Next to each name is written a 1 if the student passed the exam or a 2 if the student failed.
Your program should analyze the results of the exam as follows:
1. Input each test result (i.e., a 1 or a 2). Display the message “Enter result” on the screen each time the program requests another test result.
2. Count the number of test results of each type.
3. Display a summary of the test results indicating the number of students who passed and the number who failed.
4. If more than eight students passed the exam, print the message “Raise tuition.”
After reading the problem statement carefully, we make the following observations:
1. The program must process test results for 10 students. A counter-controlled loop can be used because the number of test results is known in advance.
2. Each test result has a numeric value—either a 1 or a 2. Each time the program reads a test result, the program must determine whether the number is a 1 or a 2.
3. Two counters are used to keep track of the exam results—one to count the number of students who passed the exam and one to count the number of students who failed the exam.
4. After the program has processed all the results, it must decide whether more than eight students passed the exam.
The Java class that implements the preceding is shown in Fig. 4.9, and two sample executions appear in Fig. 4.10.
Fig. 4.9. Nested control structures: Examination-results problem.
Fig. 4.10. Test program for class Analysis
(Fig. 4.9).
Lines 13–16 of Fig. 4.9 declare the variables that method processExamResults
of class Analysis
uses to process the examination results. Several of these declarations use Java’s ability to incorporate variable initialization into declarations (passes
is assigned 0
, failures
is assigned 0
and studentCounter
is assigned 1
).
The while
statement (lines 19–33) loops 10 times. During each iteration, the loop inputs and processes one exam result. Notice that the if
...else
statement (lines 26–29) for processing each result is nested in the while
statement. If the result
is 1
, the if
...else
statement increments passes
; otherwise, it assumes the result
is 2
and increments failures
. Line 32 increments studentCounter
before the loop condition is tested again at line 19. After 10 values have been input, the loop terminates and line 36 displays the number of passes
and failures
. The if
statement at lines 39–40 determines whether more than eight students passed the exam and, if so, outputs the message "Raise Tuition"
.
Error-Prevention Tip 4.2
Initializing local variables when they are declared helps the programmer avoid any compilation errors that might arise from attempts to use uninitialized data. While Java does not require that local variable initializations be incorporated into declarations, it does require that local variables be initialized before their values are used in an expression.
AnalysisTest
Class That Demonstrates Class Analysis
Class AnalysisTest
(Fig. 4.10) creates an Analysis
object (line 8) and invokes the object’s processExamResults
method (line 9) to process a set of exam results entered by the user. Figure 4.10 shows the input and output from two sample executions of the program. During the first sample execution, the condition at line 39 of method processExamResults
in Fig. 4.9 is true—more than eight students passed the exam, so the program outputs a message indicating that the tuition should be raised.
Java provides several compound assignment operators for abbreviating assignment expressions. Any statement of the form
variable = variable operator expression;
where operator is one of the binary operators +
, -
, *
, /
or %
(or others we discuss later in the text) can be written in the form
variable operator= expression;
For example, you can abbreviate the statement
c = c + 3;
with the addition compound assignment operator, +=
, as
c += 3;
The +=
operator adds the value of the expression on the right of the operator to the value of the variable on the left of the operator and stores the result in the variable on the left of the operator. Thus, the assignment expression c+=3
adds 3
to c
. Figure 4.11 shows the arithmetic compound assignment operators, sample expressions using the operators and explanations of what the operators do.
Fig. 4.11. Arithmetic compound assignment operators.
Java provides two unary operators for adding 1 to or subtracting 1 from the value of a numeric variable. These are the unary increment operator, ++
, and the unary decrement operator, --
, which are summarized in Fig. 4.12. A program can increment by 1 the value of a variable called c
using the increment operator, ++
, rather than the expression c=c+1
or c+=1
. An increment or decrement operator that is prefixed to (placed before) a variable is referred to as the prefix increment or prefix decrement operator, respectively. An increment or decrement operator that is postfixed to (placed after) a variable is referred to as the postfix increment or postfix decrement operator, respectively.
Fig. 4.12. Increment and decrement operators.
Using the prefix increment (or decrement) operator to add (or subtract) 1 from a variable is known as preincrementing (or predecrementing) the variable. Preincrementing (or predecrementing) a variable causes the variable to be incremented (decremented) by 1, and then the new value of the variable is used in the expression in which it appears. Using the postfix increment (or decrement) operator to add (or subtract) 1 from a variable is known as postincrementing (or postdecrementing) the variable. Postincrementing (or postdecrementing) the variable causes the current value of the variable to be used in the expression in which it appears, and then the variable’s value is incremented (decremented) by 1.
Good Programming Practice 4.4
Unlike binary operators, the unary increment and decrement operators should be placed next to their operands, with no intervening spaces.
Figure 4.13 demonstrates the difference between the prefix increment and postfix increment versions of the ++
increment operator. The decrement operator (--
) works similarly. Note that this example contains only one class, with method main
performing all the class’s work. In this chapter and in Chapter 3, you have seen examples consisting of two classes—one class containing methods that perform useful tasks and one containing method main
, which creates an object of the other class and calls its methods. In this example, we simply want to show the mechanics of the ++
operator, so we use only one class declaration containing method main.
Occasionally, when it does not make sense to create a reusable class to demonstrate a simple concept, we’ll use a “mechanical” example contained entirely within the main
method of a single class.
Fig. 4.13. Preincrementing and postincrementing.
Line 11 initializes the variable c
to 5
, and line 12 outputs c
’s initial value. Line 13 outputs the value of the expression c++
. This expression postincrements the variable c
, so c
’s original value (5
) is output, then c
’s value is incremented (to 6). Thus, line 13 outputs c
’s initial value (5
) again. Line 14 outputs c
’s new value (6
) to prove that the variable’s value was indeed incremented in line 13.
Line 19 resets c
’s value to 5
, and line 20 outputs c
’s value. Line 21 outputs the value of the expression ++c
. This expression preincrements c
, so its value is incremented, then the new value (6
) is output. Line 22 outputs c
’s value again to show that the value of c
is still 6
after line 21 executes.
The arithmetic compound assignment operators and the increment and decrement operators can be used to simplify program statements. For example, the three assignment statements in Fig. 4.9 (lines 27, 29 and 32)
passes = passes + 1;
failures = failures + 1;
studentCounter = studentCounter + 1;
can be written more concisely with compound assignment operators as
passes += 1;
failures += 1;
studentCounter += 1;
with prefix increment operators as
++passes;
++failures;
++studentCounter;
or with postfix increment operators as
passes++;
failures++;
studentCounter++;
When incrementing or decrementing a variable in a statement by itself, the prefix increment and postfix increment forms have the same effect, and the prefix decrement and postfix decrement forms have the same effect. It is only when a variable appears in the context of a larger expression that preincrementing and postincrementing the variable have different effects (and similarly for predecrementing and postdecrementing).
Common Programming Error 4.9
Attempting to use the increment or decrement operator on an expression other than one to which a value can be assigned is a syntax error. For example, writing ++(x + 1)
is a syntax error because (x + 1)
is not a variable.
Figure 4.14 shows the precedence and associativity of the operators we have introduced to this point. The operators are shown from top to bottom in decreasing order of precedence. The second column describes the associativity of the operators at each level of precedence. The conditional operator (?:
); the unary operators increment (++
), decrement (--
), plus (+
) and minus (-
); the cast operators and the assignment operators =
, +=
, -=
, *=
, /=
and %=
associate from right to left. All the other operators in the operator precedence chart in Fig. 4.14 associate from left to right. The third column lists the type of each group of operators.
Fig. 4.14. Precedence and associativity of the operators discussed so far.
The table in Appendix D, lists the eight primitive types in Java. Like its predecessor languages C and C++, Java requires all variables to have a type. For this reason, Java is referred to as a strongly typed language.
In C and C++, programmers frequently have to write separate versions of programs to support different computer platforms, because the primitive types are not guaranteed to be identical from computer to computer. For example, an int
value on one machine might be represented by 16 bits (2 bytes) of memory, and on another machine by 32 bits (4 bytes) of memory. In Java, int
values are always 32 bits (4 bytes).
Portability Tip 4.1
Unlike C and C++, the primitive types in Java are portable across all computer platforms that support Java.
Each type in Appendix D is listed with its size in bits (there are eight bits to a byte) and its range of values. Because the designers of Java want it to be maximally portable, they use internationally recognized standards for both character formats (Unicode; for more information, visit www.unicode.org) and floating-point numbers (IEEE 754; for more information, visit grouper.ieee.org/groups/754/).
Recall from Section 3.5 that variables of primitive types declared outside of a method as fields of a class are automatically assigned default values unless explicitly initialized. Instance variables of types char
, byte
, short
, int
, long
, float
and double
are all given the value 0
by default. Instance variables of type boolean
are given the value false
by default. Reference-type instance variables are initialized by default to the value null
.
In Section 3.9, we began the first stage of an object-oriented design (OOD) for our ATM system—analyzing the requirements document and identifying the classes needed to implement the system. We listed the nouns and noun phrases in the requirements document and identified a separate class for each one that plays a significant role in the ATM system. We then modeled the classes and their relationships in a UML class diagram (Fig. 3.21). Classes have attributes (data) and operations (behaviors). Class attributes are implemented in Java programs as fields, and class operations are implemented as methods. In this section, we determine many of the attributes needed in the ATM system. In Chapter 5, we examine how these attributes represent an object’s state. In Chapter 6, we determine class operations.
Consider the attributes of some real-world objects: A person’s attributes include height, weight and whether the person is left-handed, right-handed or ambidextrous. A radio’s attributes include its station setting, its volume setting and its AM or FM setting. A car’s attributes include its speedometer and odometer readings, the amount of gas in its tank and what gear it is in. A personal computer’s attributes include its manufacturer (e.g., Dell, Sun, Apple or IBM), type of screen (e.g., LCD or CRT), main memory size and hard disk size.
We can identify many attributes of the classes in our system by looking for descriptive words and phrases in the requirements document. For each one we find that plays a significant role in the ATM system, we create an attribute and assign it to one or more of the classes identified in Section 3.9. We also create attributes to represent any additional data that a class may need, as such needs become clear throughout the design process.
Figure 4.15 lists the words or phrases from the requirements document that describe each class. We formed this list by reading the requirements document and identifying any words or phrases that refer to characteristics of the classes in the system. For example, the requirements document describes the steps taken to obtain a “withdrawal amount,” so we list “amount” next to class Withdrawal
.
Fig. 4.15. Descriptive words and phrases from the ATM requirements.
Figure 4.15 leads us to create one attribute of class ATM
. Class ATM
maintains information about the state of the ATM. The phrase “user is authenticated” describes a state of the ATM (we introduce states in Section 5.9), so we include userAuthenticated
as a Boolean
attribute (i.e., an attribute that has a value of either true
or false
) in class ATM
. Note that the Boolean
attribute type in the UML is equivalent to the boolean
type in Java. This attribute indicates whether the ATM has successfully authenticated the current user—userAuthenticated
must be true
for the system to allow the user to perform transactions and access account information. This attribute helps ensure the security of the data in the system.
Classes BalanceInquiry
, Withdrawal
and Deposit
share one attribute. Each transaction involves an “account number” that corresponds to the account of the user making the transaction. We assign an integer attribute accountNumber
to each transaction class to identify the account to which an object of the class applies.
Descriptive words and phrases in the requirements document also suggest some differences in the attributes required by each transaction class. The requirements document indicates that to withdraw cash or deposit funds, users must input a specific “amount” of money to be withdrawn or deposited, respectively. Thus, we assign to classes Withdrawal
and Deposit
an attribute amount
to store the value supplied by the user. The amounts of money related to a withdrawal and a deposit are defining characteristics of these transactions that the system requires for these transactions to take place. Class BalanceInquiry
, however, needs no additional data to perform its task—it requires only an account number to indicate the account whose balance should be retrieved.
Class Account
has several attributes. The requirements document states that each bank account has an “account number” and “PIN,” which the system uses for identifying accounts and authenticating users. We assign to class Account
two integer attributes: accountNumber
and pin
. The requirements document also specifies that an account maintains a “balance” of the amount of money in the account and that money the user deposits does not become available for a withdrawal until the bank verifies the amount of cash in the deposit envelope, and any checks in the envelope clear. An account must still record the amount of money that a user deposits, however. Therefore, we decide that an account should represent a balance using two attributes: availableBalance
and totalBalance
. Attribute availableBalance
tracks the amount of money that a user can withdraw from the account. Attribute totalBalance
refers to the total amount of money that the user has “on deposit” (i.e., the amount of money available, plus the amount waiting to be verified or cleared). For example, suppose an ATM user deposits $50.00 into an empty account. The totalBalance
attribute would increase to $50.00 to record the deposit, but the availableBalance
would remain at $0. [Note: We assume that the bank updates the availableBalance
attribute of an Account
some length of time after the ATM transaction occurs, in response to confirming that $50 worth of cash or checks was found in the deposit envelope. We assume that this update occurs through a transaction that a bank employee performs using some piece of bank software other than the ATM. Thus, we do not discuss this transaction in our case study.]
Class CashDispenser
has one attribute. The requirements document states that the cash dispenser “begins each day loaded with 500 $20 bills.” The cash dispenser must keep track of the number of bills it contains to determine whether enough cash is on hand to satisfy withdrawal requests. We assign to class CashDispenser
an integer attribute count
, which is initially set to 500
.
For real problems in industry, there is no guarantee that requirements documents will be rich enough and precise enough for the object-oriented systems designer to determine all the attributes or even all the classes. The need for additional classes, attributes and behaviors may become clear as the design process proceeds. As we progress through this case study, we too will continue to add, modify and delete information about the classes in our system.
The class diagram in Fig. 4.16 lists some of the attributes for the classes in our system—the descriptive words and phrases in Fig. 4.15 lead us to identify these attributes. For simplicity, Fig. 4.16 does not show the associations among classes—we showed these in Fig. 3.21. This is a common practice of systems designers when designs are being developed. Recall from Section 3.9 that in the UML, a class’s attributes are placed in the middle compartment of the class’s rectangle. We list each attribute’s name and type separated by a colon (:
), followed in some cases by an equal sign (=
) and an initial value.
Fig. 4.16. Classes with attributes.
Consider the userAuthenticated
attribute of class ATM
:
userAuthenticated : Boolean = false
This attribute declaration contains three pieces of information about the attribute. The attribute name is userAuthenticated
. The attribute type is Boolean
. In Java, an attribute can be represented by a primitive type, such as boolean
, int
or double
, or a reference type like a class—as discussed in Chapter 3. We’ve chosen to model only primitive-type attributes in Fig. 4.16—we discuss the reasoning behind this decision shortly. [Note: The attribute types in Fig. 4.16 are in UML notation. We’ll associate the types Boolean
, Integer
and Double
in the UML diagram with the primitive types boolean
, int
and double
in Java, respectively.]
We can also indicate an initial value for an attribute. The userAuthenticated
attribute in class ATM
has an initial value of false
. This indicates that the system initially does not consider the user to be authenticated. If an attribute has no initial value specified, only its name and type (separated by a colon) are shown. For example, the accountNumber
attribute of class BalanceInquiry
is an integer. Here we show no initial value, because the value of this attribute is a number that we do not yet know. This number will be determined at execution time based on the account number entered by the current ATM user.
Figure 4.16 does not include any attributes for classes Screen
, Keypad
and DepositSlot
. These are important components of our system, for which our design process simply has not yet revealed any attributes. We may still discover some, however, in the remaining phases of design or when we implement these classes in Java. This is perfectly normal.
Software Engineering Observation 4.1
At early stages in the design process, classes often lack attributes (and operations). Such classes should not be eliminated, however, because attributes (and operations) may become evident in the later phases of design and implementation.
Note that Fig. 4.16 also does not include attributes for class BankDatabase
. Recall from Chapter 3 that in Java, attributes can be represented by either primitive types or reference types. We have chosen to include only primitive-type attributes in the class diagram in Fig. 4.16 (and in similar class diagrams throughout the case study). A reference-type attribute is modeled more clearly as an association (in particular, a composition) between the class holding the reference and the class of the object to which the reference points. For example, the class diagram in Fig. 3.21 indicates that class BankDatabase
participates in a composition relationship with zero or more Account
objects. From this composition, we can determine that when we implement the ATM system in Java, we’ll be required to create an attribute of class BankDatabase
to hold references to zero or more Account
objects. Similarly, we can determine reference-type attributes of class ATM
that correspond to its composition relationships with classes Screen
, Keypad
, CashDispenser
and DepositSlot
. These composition-based attributes would be redundant if modeled in Fig. 4.16, because the compositions modeled in Fig. 3.21 already convey the fact that the database contains information about zero or more accounts and that an ATM is composed of a screen, keypad, cash dispenser and deposit slot. Software developers typically model these whole/part relationships as compositions rather than as attributes required to implement the relationships.
The class diagram in Fig. 4.16 provides a solid basis for the structure of our model, but the diagram is not complete. In Section 5.9, we identify the states and activities of the objects in the model, and in Section 6.19 we identify the operations that the objects perform. As we present more of the UML and object-oriented design, we’ll continue to strengthen the structure of our model.
4.1 We typically identify the attributes of the classes in our system by analyzing the _________ in the requirements document.
a. nouns and noun phrases
b. descriptive words and phrases
c. verbs and verb phrases
d. All of the above.
4.2 Which of the following is not an attribute of an airplane?
a. length
b. wingspan
c. fly
d. number of seats
4.3 Describe the meaning of the following attribute declaration of class CashDispenser
in the class diagram in Fig. 4.16:
count : Integer = 500
4.1 b.
4.2 c. Fly is an operation or behavior of an airplane, not an attribute.
4.3 This indicates that count
is an Integer
with an initial value of 500
. This attribute keeps track of the number of bills available in the CashDispenser
at any given time.
Only three types of control structures—sequence, selection and repetition—are needed to develop any problem-solving algorithm. Specifically, this chapter demonstrated the if
single-selection statement, the if
...else
double-selection statement and the while
repetition statement. We used control-statement stacking to total and compute the average of a set of student grades with counter- and sentinel-controlled repetition, and we used control-statement nesting to analyze and make decisions based on a set of exam results. We introduced Java’s compound assignment operators, and its increment and decrement operators. Finally, we discussed Java’s primitive types. In Chapter 5, we continue our discussion of control statements, introducing the for
, do
...while
and switch
statements.