4. Control Statements: Part 1

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

4.1 Introduction

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.

4.2 Control Structures

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.

Sequence Structure in Java

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.

Image

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 18 and 10, or visit www.uml.org.

Selection Statements in Java

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

Repetition Statements in Java

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.

Summary of Control Statements in Java

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.

4.3 if Single-Selection Statement

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

Image

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.

4.4 if...else Double-Selection Statement

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

Image

Conditional Operator (?:)

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.

Good Programming Practice 4.1

Image

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.

Nested if...else Statements

A 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:

Image

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

Image

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.

Dangling-else Problem

The 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,

Image

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

Image

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:

Image

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.

Blocks

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:

Image

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

Image

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

Image

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

Image

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

4.5 while Repetition Statement

A 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:

Image

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.

Common Programming Error 4.3

Image

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.

Image

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.

4.6 Counter-Controlled Repetition

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.

Implementing Counter-Controlled Repetition in Class 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.

Image

Image

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.

Common Programming Error 4.4

Image

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.

Image

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

Notes on Integer Division and Truncation

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

Image

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.

4.7 Sentinel-Controlled Repetition

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

Image

Choosing a sentinel value that is also a legitimate data value is a logic error.

Implementing Sentinel-Controlled Repetition in Class 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.

Image

Image

Image

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

Program Logic for Sentinel-Controlled Repetition vs. Counter-Controlled Repetition

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

Image

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

Image

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

Image

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.

Explicitly and Implicitly Converting Between Primitive Types

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

Image

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.

Image

4.8 Nested Control Statements

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.

Image

Fig. 4.10. Test program for class Analysis (Fig. 4.9).

Image

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

Image

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.

4.9 Compound Assignment Operators

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.

Image

4.10 Increment and Decrement 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.

Image

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

Image

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.

Image

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

Image

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.

Image

4.11 Primitive Types

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

Image

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.

4.12 (Optional) Software Engineering Case Study: Identifying Class Attributes

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.

Identifying Attributes

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.

Image

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.

Modeling Attributes

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.

Image

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

Image

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.

Software Engineering Case Study Self-Review Exercises

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

Answers to Software Engineering Case Study Self-Review Exercises

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.

4.13 Wrap-Up

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset