6. Methods: Communicating with Objects

6.6. Flow of Control: Control Structures

We have been ignoring a couple of problems with the definition of the OneRowNim class. One problem is that we would describe a One Row Nim game as two players taking turns until there are no more sticks. An object using OneRowNim would need a way to repeatedly execute a group of statements. One command in Java that controls the repetition of a block of statements is called a while loop. We will consider it later in this section.

A second problem is with the definition of takeSticks():

,,

 

 

 

J

 

 

 

 

 

 

 

 

 

 

 

 

Simple if statement


It is possible to call this method with an argument greater than 3 or less than 1. The call game.takeSticks(5) will remove 5 sticks even though the rules of One Row Nim say that you must remove 1, 2, or 3. While one might assume that the user interface should prevent the user from break- ing this rule, it is a far better design if it was dealt with in OneRowNim. To do this we need a Java structure that executes different statements de- pending on whether the parameter is greater than 3, less than 1, or be- tween 1 and 3. The Java if-else statement has this capability. A fuller treat- ment of control structures appears in Chapter 6, but in this section, we will briefly introduce a couple of simple control structures. This will enable us to write programs that take more interesting actions.

The Simple If Statement

A selection control structure, allows a program to select between two or more alternative paths of execution. The if statement is the most basic selection control structure in Java. Most programming languages have its equivalent.

 

 

 

 

 

 

 

 

The statement contained in the if statement can be any valid Java state- ment, including a compound statement. (Recall from Chapter 1 that a compound statement is a set of statements contained within curly braces.) The boolean expression is an expression that is either true or false. We have seen examples of boolean expressions that involve int variables, int values, and the inequality or equality operators. A method call to a method with a boolean result type is another example of a boolean expression. Given this description of if statement syntax, the following are examples of valid if statements:

,,

 

J

For readability, we usually write an if statement with its contained state- ment indented on the next line:

,,

 

 

 

J

 

The following are all examples of syntax errors involving the if statement:

,,

 

 

 

 

 

J

Semantically, the if statement has the following interpretation: First, the boolean condition is evaluated. If it is true, then the contained statement is executed; if it is false, then the contained statement is not executed. This is shown in Figure 3.11. The flowchart clearly shows that program flow will take one or the other of the alternative paths coming out of the diamond-

shaped boolean condition box. The branch through the rectangular state-

ment box will be taken when the boolean condition is true; otherwise the

 

statement will be skipped.

As another example, consider the definition of a getPlayerString() method for the OneRowNim class:


Figure 3.11: Flowchart of the if statement. Diamond-shaped sym- bols at the branch points contain

 

,,boolean expressions. Rectangu-

lar symbols can only contain ex- ecutable statements. Circles act simply as connectors, to connect two or more paths.

 

 

 

 

 

The flowchart in Figure 3.12 shows the program flow of the entire

getPlayerString() method.It is important to note that when a


J

 

 

Figure 3.12:Flowchart of the

getPlayerString() method.

 

 

 

 

 

 

 

 

return statement is executed in a method, control is returned im-

 

 

 

 

 

 

 

 

Compound statement


mediately to the calling method. Thus, if player == 1 is true, the string “Player One” is returned to the calling method and the getPlayerString() method exits at this point. If it is false, then player == 2 should be true (if we have a consistent state) and the string “Player Two” should be returned and the method exited. Thus, if we have a consistent state —that is, if player has value 1 or 2—then the third return statement should never be reached.

The following example shows the more common case where the state-

ment contained in an if statement can be a compound statement:

,,

 

 

 

 

 

 

 

Local scope


J

If player == 1 is true, then all four statements in the contained com- pound statement will be executed. Note here that we are declaring the local variable, s, in this block. Its scope would extend only to the end of the block. Note also that when we use a compound statement, the com- pound statement itself is not followed by a semicolon because it is already enclosed in braces.

A common programming error is to forget the braces around the com- pound statement. Merely indenting the statements following the if clause doesn’t alter the logic of the if statement. For example, the following if statement still has only one statement in its if clause:

,,

 

 

 

J

This segment will always print “Two” because the second println() is not part of the if statement. To include it in the if statement, you must enclose both println() statements within braces:

,,

 

 

 

J

 

The if-else Statement

A second version of the if statement incorporates an else clause into the structure. This allows us to execute either of two separate statements (sim-

 

ple or compound) as the result of one boolean expression. For example, the statement

,,

 

 

 

J

will print “Player One” if player == 1 is true. Otherwise, it will print “Player Two”.

 

 

 

 

 

 

As in the case of the simple if statement, the keyword if is followed by If-else syntax

a parenthesized boolean expression, which is followed by statement1, which may be either simple or compound. If statement1 is a simple statement, then it is followed by a semicolon. The else clause follows immediately after statement1. It begins with the keyword else, which is followed by statement2, which can also be either a simple or compound statement. Note that there is no boolean expression following the else keyword. In an if-else statement, the boolean expression following the keyword if goes with both the if and else clauses.

Semantically, the if-else statement has the following interpretation: If the boolean expression is true, execute statement1; otherwise execute state- ment2. This interpretation is shown in Figure 3.13.

The Nested if/else Multiway Selection Structure

 

The statements that one inserts in place of statement1 and statement2 in the if-else statement can be any executable statement, including another if statement or if-else statement. In other words, it is possible to embed one or more if-else statements inside another if-else statement, thereby creating a nested control structure. As with most things, making a control structure too complex isn’t a good idea, but there is a standard nested if- else control structure that is very useful. It is known as multiway selec- tion. As shown in Figure 3.14, the multiway structure is used when you want to select one and only one option from several alternatives.

Suppose we have an int variable num that will contain one of the val- ues 1, 2, or 3 unless there has been an error assigning a value to it. Sup- pose that we want to write code that will write out the English word for


Figure 3.13:Flowchart of the

if-else statement.

 

Figure 3.14: Flowchart of a nested if-else statement.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

the value in num. In the example shown in Figure 3.14 there are three alternatives plus an error state. Here is the Java code for this example:

,,

 

 

 

 

 

 

 

Multiple alternatives


J

Note that the multiway structure has a single entry point and that only one of the four possible alternatives is executed. The code will print exactly one of the strings.

We will have many occasions to use the if-else structure. Al- though it does not represent a significant change, we could rewrite our takeStick() method to make use of the if-else instead of the somewhat obscure statement :

 

,,

 

J

 

to change the value of player from 1 to 2 or vice versa:

,,

 

 

 

 

 

J

In some respects this version of takeSticks() involves four lines of code instead of one but is simpler to understand. The if-statement tests whether the value of player is 1. If it is, the value is changed to 2. If the value of player is not 1, then the value must be 2 and so the value is changed to 1. Both versions of the code will give precisely the same result, a programmer could choose to write the code either way.

 

SELF-STUDY EXERCISES

EXERCISE 3.10 Consider the following method with boolean param- eter.

,,

 

 

 

 

J

Draw a flowchart for the if-else version of the getStatus() method, us- Flowchart symbols

ing the figures in this section as a guide. The if-else structure should be drawn exactly as shown in Figure 3.11. It should have a single entry point that leads directly to the top of a diamond-shaped box that contains a boolean condition. There should be two branches coming out of the con- dition box. The one going to the right is the true case, and the one going to the left is the false case. Each of these branches should contain one rectangular box, which contains the statements that would be executed in that case. The left and right branches should be connected by a circular symbol that is aligned directly under the diamond box whose conditions it connects. There should be a single exit arrow pointing directly down.

EXERCISE 3.11 Identify the error in the following statements:

,,

 

 

 

 

 

 

J

 

EXERCISE 3.12 Suppose we have an int instance variable named player in some class describing a three person game. Write a method named getPlayerName() that returns a String. It should return “Ann”, “Bill”, “Cal”, or “Error” when the value of player is respectively 1, 2, 3, or any other value.

EXERCISE 3.13 How does a parameter for a primitive type differ from a parameter for a reference type?

The While Structure

A repetition structure is a control structure that repeats a statement or sequence of statements in a controlled way. Repetition structures are also referred to as loop structures. Many types of programming tasks require a repetition structure. Consider some examples.

You want to add up the squares of the numbers from 1 to 100.

You want to compute compound interest on an amount of money in a savings account with a fixed interest rate if it is kept there for 30 years.

A computer security employee wants to try every possible password in order to break into an account of a suspected spy.

You want to have players input moves for a turn in a game until the game is over. Our OneRowNim is such an example.

 

We will study several different repetition structures of Java in depth in Chapter 6. We will briefly consider the while statement here so as to be able to define methods that are more powerful and more interesting. Let us write a method that solves a slight generalization of the first problem above. We will use the while statement to sum the squares of integers from 1 to a number specified as a parameter of the method. Thus, the method call sumSquares(3) should return the value 14 since 1 1 + 2 2 + 3 3 =

1 + 4 + 9 = 14.

,,

 

 

 

 

 

 

J

Note that in this example, the variable num gets assigned an initial value of 1 before the while statement. Note also that the boolean expression num < max in parentheses after while states the condition for which we wish to continue summing squares. Finally note that the last statement in the block following the boolean expression adds 1 to num–that is, this variable is updated at the end of the block.

 

The while statement is a loop statement in which the loop entry condi- tion occurs before the loop body. It has the following general form:

 

When the while statement is executed, the loop entry condition is evalu- ated and if this evaluates to false, execution continues at the statement immediately after the loop body. If the loop entry condition evaluates to true, the loop body is executed and then the entry condition is evalu- ated again. The loop body continues to be executed until the loop entry condition evaluates to false.

To have a while statement accomplish a task, the variable or variables in the loop entry condition must be initialized correctly before the while statement and these variables must be correctly updated at the end of the loop body. We can refer to the initializer statement followed by a while statement as a while structure. We can restate the above guidelines as a design principle:

 

 

In pseudocode, the while structure would take the following form:

,,

 

 

 

J

As its form suggests, the while structure is designed so that on some con- ditions the loop body will never be executed. Because it tests for the loop entry condition before the loop body, it is possible that the loop body is never executed. We might say that it is designed to perform 0 or more iterations.

For example, if the method call sumSquares(-3) is executed, the loop body will be skipped, because the loop entry condition num <= max is false to begin with. No iterations will be performed, and the algorithm will simply return the value 0.

Note also that in the while statement the bound test is preceded by initializer statements, and the loop body contains updater statements. The semantics of the while structure are shown in Figure 3.15.

 

Figure 3.15: Flowchart of the while statement and while struc- ture.


While StatementWhile Structure

 

 

 

 

 

 

 

 

 

 

 

 

SELF-STUDY EXERCISE

 

 

EXERCISE 3.14 Modify the definition of the sumSquares() method to define a method named sumCubes() that sums the cubes of integers from a minimum value up to a maximum value and returns that sum. sumCubes() should have two parameters that will store the minimum and maximum values. Thus the method call sumCubes(2,3) should return 35 since 2 2 2 + 3 3 3 = 8 + 27 = 35.

 

 

 

Testing an Improved OneRowNim

 

Let’s use the control structures that we have discussed to improve the definition of the takeSticks() method of OneRowNim. We noted ear- lier that our current definition allows 4 or more sticks to be removed from nSticks even though the rules of One Row Nim indicate that a player must take one, two, or three sticks on a turn. We can use if-else statements to make certain that no more than 3 sticks get removed.

What should happen if the method takeSticks() is called with an argument that does not represent a legal number of sticks to remove? In this case, it would probably make sense to remove no sticks at all and to keep the value of player the same so that the player whose turn it is does not change. In addition, it would be nice if the method could signal that an illegal move has been attempted. This can be accomplished if we redefine takeSticks() to return a boolean value. Let’s have a return value of true represent the case that a valid number of sticks have been removed and the player to play next has been changed. A return of false will indicate that an illegal move has been attempted. Making these changes

 

to the takeSticks() method will yield a method definition that looks like:

,,

 

 

 

 

 

 

 

 

J

Notice that the new definition of the takeSticks() method has a boolean return type. Also notice that the if/else multiway structure is used to handle the three cases of the parameter num being less than one, more than three, or a valid number.

Let us add one more method to the OneRowNim class. Let’s define a method called getWinner()that will return the number of the winning player if the game is over. Recall that the player who takes the last stick loses, so after that last play, the player whose turn it is to play next is the winner. However, we should be concerned about what value to return if the game is not over when the method is called. A common strategy is to have a method return a special value to indicate that it is in a state in which it cannot return the value requested. Returning a 0 value is a good way to indicate that the game is not over so a winner cannot be identified. With this information, the if/else statement can be used in the definition of getWinner().

,,

 

 

 

 

J

We now have the final version (for this chapter) of the OneRowNim class whose implementation is given in Figure 3.16. We have turned a very simple class into one that contains quite a few elements. Compared to our first version (in Chapter 1), this Chapter’s version of OneRowNim presents an interface (to other objects) that is easy and convenient to use. The constructor methods with parameters provide an easy way to create a OneRowNim instance with any number of sticks. The use of private instance variables and a single, carefully designed mutator method, takeSticks(), prevents other objects from tampering with the state of a OneRowNim object’s state. The other methods provide a flexible way to find out the state of a OneRowNim object. The complete implementation of this OneRowNim is shown in Figure 3.16.

 

,,

public c l a s s OneRowNim

private in t n St i c ks = 7 ;

private in t player = 1 ;

 

public OneRowNim( )

{

} // One Row Nim ( ) c o n s t r u c t o r

public OneRowNim( in t s t i c k s )

{n St i c ks = s t i c k s ;

}// One Row Nim ( ) c o n s t r u c t o r 2

public OneRowNim( in t s t i c k s , in t s t a r t e r ) n St i c ks = s t i c k s ;

player = s t a r t e r ;

}// One Row Nim ( ) c o n s t r u c t o r 3

public boolean t a k e S t i c k s ( in t num)

i f (num < 1 ) return fa l s e ;// E r r o r e ls e i f ( num > 3 ) return fa l s e ; // E r r o r e ls e// t h i s i s a v a l i d move

n S t i c k s = n St i c ks num; player = 3 player ;

return t rue ;

} // e l s e

} // t a k e S t i c k s ( )

public in t g e t S t i c k s ( )

{return n St i c ks ;

} // g e t S t i c k s ( )

public in t get Player ( )

{return player ;

} // g e t P l a y e r ( )

public boolean gameOver ( )

{return ( n St i c ks <= 0 ) ;

} // g a m e O v e r ( )

public in t getWinner ( )

i f ( n S t i c k s < 1 ) return get Player ( ) ;

e ls e return 0 ;// ga m e i s n o t o v e r

} // g e t W i n n e r ( )

public void report ( )

System . out . p r i n t l n ( ”Number of s t i c k s l e f t : +

g e t S t i c k s ( ) ) ; System . out . p r i n t l n ( ”Next turn by player +

get Player ( ) ) ;

}// r e p o r t ( )

// One Row Nim c l a s s

\J

Figure 3.16: The OneRowNim class with improved methods.

 

Let’s use a while statement to test the new methods of the class. A pseudocode description of how a game is played might look like:

,,

 

 

 

 

 

J

Translating this pseudocode into Java code in a main() method in a sepa- rate class gives us the class shown in Figure 3.17. We will use the Scanner class introduced in the previous chapter to get moves from the keyboard

 

 

 

,,

 

 

 

 

 

 

 

 

 

 

 

 

 

J

Figure 3.17: The TestOneRowNim class with a while loop.

 

 

 

 

for both players. Before each move game.report() describes the state of the game before the user is prompted to input a move for one of the players. A reader interested in seeing the lengthy output to the console when the TestOneRowNim class is run is encouraged to actually run the program.

Note that the return value of the takeSticks() method is ignored in this test program. We will make use of the return value in test pro- grams in the next chapter when better user interfaces are developed for OneRowNim. Note, however, that taken together, the public methods for

 

OneRowNim provide other objects with an interface that they can use to communicate with individual OneRowNim objects.

 

 

Object-oriented design


To reiterate a point made at the outset, object-oriented programming is a process of constructing objects that will interact with each other. Object- oriented programs must ensure that the objects themselves are well de- signed in terms of their ability to carry out their designated functions. Good design in this sense requires careful selection of instance variables and careful design of methods to ensure that the object can carry out its assigned tasks. However, equal care must be taken to ensure that the interactions that take place among objects are constrained in ways that make sense for that particular program. This aspect of designing ob- jects comes into play in designing the methods—constructor, accessor, and mutator—that make up the object’s interface.